开篇介绍:
hello 大家,本篇博客我们来学习JavaScript基础语法
其实大家有了C++的基础之后,学习这个完全可以说是简单~
一、变量:编程世界的 "收纳盒",从根源讲透每一个细节
在正式讲语法之前,我们必须先搞懂一个最核心的问题:为什么编程里要有变量?变量到底是个什么东西?
1.1 变量的本质:给你的数据找一个 "带名字的收纳盒"
我们用一个所有人都能懂的生活场景来类比:你每天出门,要带钥匙、手机、耳机、口红、工牌这些东西,你不会直接把这些东西全都攥在手里,更不会随手扔在口袋里乱作一团 ------ 你一定会把它们放进一个带名字的收纳包里。比如:
- 你有一个叫 "通勤包" 的包,里面放手机、钥匙、工牌;
- 你有一个叫 "化妆包" 的包,里面放口红、粉底、散粉;
- 你有一个叫 "数码包" 的包,里面放耳机、充电器、U 盘。
这个 **"带名字的收纳盒 / 包",就是编程里的 "变量"**;而你放进包里的钥匙、手机、口红,就是变量里要存的 "数据"。
这个类比,会贯穿我们整个变量的讲解,因为它能帮你彻底理解变量的所有特性:
- 包要有名字:你总不能喊 "那个包",别人不知道你说的是哪个,所以变量必须有 "变量名";
- 包能装东西:你可以把东西放进包里,这个动作在编程里叫 "赋值";
- 包能换东西:你可以把包里的手机拿出来,放进去一个平板,这个动作叫 "修改变量的值";
- 不同的包能装不同的东西:小卡包装钥匙,大背包装电脑,而 JavaScript 里的包是 "万能收纳盒"------ 今天装水,明天装米,后天装水果,想装什么就装什么,这就是我们后面要讲的 "动态类型"。
理解了这个本质,我们再来看变量的每一个语法细节,你就不会再是死记硬背,而是真正懂了 "为什么要这么写"。
1.2 变量的创建:声明 + 初始化,每一步都讲透
在 JavaScript 里,创建一个变量,分为两个核心步骤:声明 和 初始化。很多新手学不好变量,就是因为没把这两个步骤分开搞懂,我们一个一个来。
1.2.1 第一步:变量声明 ------ 告诉 JS 引擎 "我要一个收纳盒"
什么是 "声明"?声明就是你拿着身份证去物业登记:"我要租一个储物柜,编号叫 xxx"------ 也就是告诉 JavaScript 引擎,我要创建一个叫 xx 名字的变量,你给我在内存里留一块位置。
在 JavaScript 里,声明变量必须用关键字,最基础的是 var,后面我们会讲 let。这里我们先把 var 讲透。
**什么是关键字?**关键字就是 JavaScript 这门语言里,天生就带 "特殊身份" 的词。就像我们身份证上的 "姓名""性别""民族",是国家规定的、有固定含义的词,你不能随便用这些词给自己起名;同理,JavaScript 里的关键字,你也不能用来当变量名,因为引擎一看到这个词,就知道它要做什么固定的操作。
var 就是一个关键字,它的固定含义就是:我要声明一个变量。
声明变量的基础语法:
var 变量名;
举个例子:
var userName;
这句话翻译成人话就是:"JavaScript 引擎你好,我要声明一个变量,名字叫 userName,你给我在内存里留一个收纳盒的位置,盒子的名字就叫 userName!"
这一步做完,你就有了一个空的收纳盒,盒子有名字了,但里面还什么都没放。
1.2.2 第二步:变量初始化 ------ 给你的收纳盒放进东西
什么是 "初始化"?初始化就是你给已经声明好的变量(空收纳盒),第一次放进东西 ------ 也就是第一次给变量赋值。
这里我们要讲一个核心中的核心,90% 的新手入门都会在这里踩坑:JavaScript 里的 = 号,不是我们数学里的 "等于",而是一个 "赋值动作"。
我们数学里写 1 + 1 = 2,这里的 = 是 "判断两边的东西完全相等",是一个结果;但在 JavaScript 里,= 号是一个动作指令 ,它的含义是:把 = 号右边的东西,放进 = 号左边的收纳盒里。
这个概念,我用十遍、百遍的文字重复,就是要让你刻在脑子里:
= 号是 "从右往左放东西",不是 "两边相等"!= 号是 "从右往左放东西",不是 "两边相等"!= 号是 "从右往左放东西",不是 "两边相等"!
初始化的基础语法:
变量名 = 要存的数据;
举个例子:
var userName; // 第一步:声明变量,拿到一个叫userName的空收纳盒
userName = '张三'; // 第二步:初始化,把字符串'张三',放进userName这个收纳盒里
当然,我们 99% 的场景里,都会把声明和初始化写在同一行,这是更简洁、更推荐的写法:
var userName = '张三'; // 声明+初始化一步完成:拿盒子+放东西,一次性做完
这句话翻译成人话就是:"JS 引擎,给我一个叫 userName 的收纳盒,同时把 ' 张三 ' 这个东西放进去!"
1.3 变量语法的细节规则:每一条都讲清 "为什么"
很多新手写代码,天天因为一些小细节报错,就是因为没把这些基础规则搞懂。我们把每一条规则都掰开,讲清 "是什么、为什么、不遵守会怎么样"。
1.3.1 必须用英文标点,绝对不能用中文标点
这是新手最最最容易犯的错误,没有之一。我们先把这条规则焊死在脑子里:
JavaScript 里所有的标点符号,必须是英文半角标点,绝对不能用中文全角标点!
**为什么?**因为 JavaScript 引擎是外国人开发的,它只认识英文的标点符号。就像你给一个只会说中文的人看一段全英文的话,他完全看不懂;同理,JS 引擎看到中文标点,根本不知道这是什么东西,只会直接报错。
在我们的眼里,中文分号 ; 和英文分号 ; 长得几乎一模一样,中文引号 "" 和英文引号 "" 也没差多少,但在 JS 引擎眼里,它们是完全不同的两个东西,就像汉字里的 "人" 和 "入",看着像,但意思完全不一样。
我们把所有新手常踩的中文标点坑,全部列出来,一个个讲:
- 分号 :语句结尾的分号,必须是英文
;,不能是中文;- 错误写法:
var name = '张三';(中文分号,直接报错) - 正确写法:
var name = '张三';(英文分号,正常执行)
- 错误写法:
- 引号 :字符串的引号,必须是英文单引号
''或英文双引号"",不能是中文''或""- 错误写法:
var name = "张三";(中文双引号,报错) - 错误写法:
var name = '张三';(中文单引号,报错) - 正确写法:
var name = '张三';(英文单引号,正常) - 正确写法:
var name = "张三";(英文双引号,正常)
- 错误写法:
- 括号 :后面我们要学的函数、if 语句、循环里的括号,必须是英文
(){}[],不能是中文(){}【】- 错误写法:
alert('你好');(中文括号,报错) - 正确写法:
alert('你好');(英文括号,正常)
- 错误写法:
- 逗号 :多个变量声明、参数里的逗号,必须是英文
,,不能是中文,- 错误写法:
var name = '张三',age = 20;(中文逗号,报错) - 正确写法:
var name = '张三', age = 20;(英文逗号,正常)
- 错误写法:
怎么避免踩这个坑? 写代码的时候,一定要把你的输入法切换到英文半角模式,尤其是输入标点的时候,一定要看一眼输入法状态。如果你的代码报了莫名其妙的语法错误,第一时间就去检查:是不是哪里用了中文标点!
1.3.2 语句结尾的分号:可以省略,但强烈建议你加上
很多新手会问:"我看别人的代码里,有的句子结尾没写分号,也能运行,那我到底要不要写?"
这里我给你一个绝对不会出错的结论:无论 JS 引擎支不支持省略分号,你都必须在每一个语句的结尾,加上英文分号;。
我们先讲清楚背后的原理,你就知道为什么要这么做了。
JavaScript 引擎有一个机制,叫做 ASI(Automatic Semicolon Insertion,自动分号插入)。简单说就是:如果你没写分号,JS 引擎会在解析代码的时候,自动给你 "猜" 哪里应该加分号,然后给你补上,让代码能正常运行。
但问题就出在 "猜" 这个字上 ------JS 引擎不是万能的,它很多时候会猜错,给你补到错误的位置,导致你的代码执行结果完全不对,甚至直接报错。
举一个最经典的、省略分号导致报错的例子:
// 你写的代码,省略了分号
var a = 10
var b = 20
(function(){
console.log(a + b)
})()
你以为 JS 引擎会给你补成分号,变成这样:
var a = 10;
var b = 20;
(function(){
console.log(a + b);
})();
但实际上,JS 引擎会把它解析成这样:
var a = 10;
var b = 20(function(){
console.log(a + b);
})();
它会把第二行的 20 和第三行的 ( 连在一起,认为你是想调用一个叫 20 的函数!但 20 根本不是函数,所以代码会直接报错:Uncaught TypeError: 20 is not a function。
这种错误,对于新手来说,根本找不到问题出在哪里 ------ 你明明看着代码没写错,却一直报错,就是因为省略分号导致的。
而你只要在每一个语句结尾都加上分号,就完全不会有这个问题,JS 引擎 100% 不会猜错你的意图。所以,哪怕省略分号能少打一个字符,我也强烈建议你:每一个语句结尾,都必须加上英文分号。
1.3.3 变量名的命名规则:不是什么名字都能起
变量名就像你的收纳盒的标签,不是什么字都能写的,必须遵守 JavaScript 的命名规则,否则会直接报错。我们把规则一条一条讲透,不仅告诉你 "不能这么做",还告诉你 "为什么不能这么做"。
规则 1:变量名只能包含 英文字母 (a-z,A-Z)、数字 (0-9)、下划线 (_)、美元符号 ($)
这是最基础的规则,变量名里只能出现这四类字符,其他任何字符都不行。
**为什么有这个规则?**因为 JS 引擎解析代码的时候,只有这四类字符,会被识别成 "变量名的一部分",其他字符都会被当成 "运算符、标点、分隔符",导致引擎解析错误。
合法的变量名:
var age; // 纯字母
var user_name; // 字母+下划线
var $price; // 美元符号+字母
var student1; // 字母+数字
var myName$; // 字母+美元符号
非法的变量名(会直接报错):
var user-name; // 错误!包含减号-,引擎会以为你要做减法运算
var my name; // 错误!包含空格,引擎会以为my和name是两个东西
var user@name; // 错误!包含@符号,引擎不识别
var 姓名; // 错误!虽然中文变量名在部分浏览器里能运行,但强烈不推荐,会有各种兼容问题
规则 2:变量名不能以数字开头
变量名的第一个字符,只能是字母、下划线、美元符号,绝对不能是数字。
为什么有这个规则? 因为 JS 引擎看到一个以数字开头的词,会第一时间认为你要写一个 "数字",而不是变量名。比如你写 var 1age;,引擎先看到 1,以为是数字,后面又看到 age,直接就懵了,不知道这是什么东西,只能给你报语法错误。
非法的变量名:
var 1age; // 错误!以数字开头
var 2student; // 错误!以数字开头
合法的写法:
var age1; // 正确!数字放在后面
var student2; // 正确!数字放在后面
规则 3:变量名严格区分大小写
JavaScript 是一门严格区分大小写 的语言,大写字母和小写字母,在引擎眼里是完全不同的字符。也就是说,Name、name、NAME、nAme,是四个完全不同的变量,互相之间没有任何关系。
举个例子:
var name = '张三';
var Name = '李四';
var NAME = '王五';
console.log(name); // 输出:张三
console.log(Name); // 输出:李四
console.log(NAME); // 输出:王五
很多新手会在这里踩坑:声明的时候用的是小写 userName,用的时候写成了大写 Username,结果代码一直报 "变量未定义" 的错误,找了半天都找不到问题,就是因为没注意大小写。
规则 4:绝对不能用 JavaScript 的关键字和保留字当变量名
我们前面讲过,关键字是 JS 里天生带固定含义的词,比如 var、if、else、for、function 等等;保留字是 JS 现在没用到,但未来版本可能会当成关键字的词,比如 class、import、export 等等。
你不能用这些词当变量名,因为 JS 引擎一看到这些词,就会执行它固定的操作,而不会把它当成变量名。
举个错误的例子:
var var = 10; // 错误!var是关键字,不能当变量名
var if = '你好'; // 错误!if是关键字,不能当变量名
var for = 20; // 错误!for是关键字,不能当变量名
常见的关键字和保留字 (不用死记硬背,写多了自然就记住了):var、let、const、function、if、else、for、while、do、switch、case、break、continue、return、true、false、null、undefined、class、import、export、default、try、catch、finally 等等。
1.3.4 变量名的命名规范:让你的代码人人都能看懂
上面的 "规则" 是必须遵守的,不遵守就会报错;而这里的 "规范" 是行业里的通用约定,不遵守不会报错,但会让你的代码变得非常难读,别人看了会骂街,你自己过一个月再看,也会忘了这个变量是干嘛的。
核心规范 1:变量名必须见名知意,绝对不要用 a、b、c 这种无意义的名字变量名的核心作用,就是让你看到名字,就知道这个变量里存的是什么数据。
反面例子(千万不要这么写):
var a = '张三'; // 谁知道a是啥?名字?年龄?地址?
var b = 20; // 谁知道b是啥?
var c = 90; // 谁知道c是啥?
正面例子(推荐这么写):
var userName = '张三'; // 一看就知道是用户名
var userAge = 20; // 一看就知道是用户年龄
var userScore = 90; // 一看就知道是用户分数
你可能觉得 "写短一点省事",但等你的代码超过 100 行,你就会发现:你自己都忘了 a、b、c 是干嘛的,还要翻到前面去看声明,反而更浪费时间。而见名知意的名字,你看一眼就懂,永远不会忘。
核心规范 2:使用小驼峰命名法小驼峰命名法是 JavaScript 行业里通用的变量命名规范,规则是:
- 变量名由多个单词组成的时候,第一个单词的首字母小写,后面每个单词的首字母都大写。
例子:
// 单个单词,全小写
var name = '张三';
var age = 20;
// 多个单词,小驼峰
var userName = '张三'; // user + Name
var userAge = 20; // user + Age
var userLoginTime = '2024-01-01'; // user + Login + Time
var productPrice = 199; // product + Price
这种写法的好处是:多个单词连在一起,每个单词的首字母大写,一眼就能看清是哪几个单词,可读性非常高。
1.4 变量的使用:先声明,后使用
变量就像动漫里的角色放技能 ------ 必须先喊出技能名字(声明变量),才能释放技能(使用变量)。如果你没声明变量,直接就用,JS 引擎会直接给你报一个致命错误:ReferenceError: 变量名 is not defined(引用错误:这个变量没定义)。
错误例子(直接使用未声明的变量):
console.log(userName); // 报错!ReferenceError: userName is not defined
var userName = '张三';
这里要提一个 JavaScript 的特殊特性:变量提升 。简单说就是,用 var 声明的变量,JS 引擎会把它的 "声明" 部分,自动提升到代码的最顶部,但是 "赋值" 部分不会提升。
比如上面的代码,JS 引擎实际解析的时候,会变成这样:
var userName; // 声明被提升到顶部
console.log(userName); // 输出 undefined,不会报错
userName = '张三'; // 赋值留在原地
虽然变量提升不会让代码报错,但会让代码的执行逻辑变得非常奇怪,你明明是先打印再声明,结果却输出了 undefined,很容易出 bug。所以,哪怕 JS 有变量提升的特性,我也强烈要求你:所有变量,必须先声明、再使用,绝对不要先使用、后声明。这是最规范、最不容易出错的写法。
1.5 变量的修改:给你的收纳盒换个东西
变量之所以叫 "变" 量,就是因为它的值是可以改变的。你可以随时给已经声明并初始化的变量,赋一个新的值,新的值会覆盖掉原来的值。
修改的语法:
变量名 = 新的值;
举个例子:
// 声明+初始化:给userName收纳盒放进'张三'
var userName = '张三';
console.log(userName); // 输出:张三
// 修改变量:把原来的'张三'拿出来,放进'李四'
userName = '李四';
console.log(userName); // 输出:李四
// 甚至可以修改成完全不同类型的值(动态类型特性)
userName = 20;
console.log(userName); // 输出:20
这里要注意:修改变量的时候,不需要再写 var 关键字了!var 只用来 "第一次声明变量",修改的时候直接写变量名 + 赋值就行。如果你重复用 var 声明同一个变量,虽然不会报错,但这是非常不好的写法,会让代码变得混乱,绝对不要这么做。
错误写法(重复声明):
var userName = '张三';
var userName = '李四'; // 错误!重复用var声明同一个变量,虽然不报错,但非常不推荐
正确写法(修改):
var userName = '张三';
userName = '李四'; // 正确!直接赋值修改,不用var
1.6 let 关键字:更现代的变量声明方式
除了 var,JavaScript 还支持用 let 关键字声明变量,它的基础用法和 var 完全一模一样:声明、初始化、修改、命名规则,都和 var 没有任何区别。
基础用法示例:
// 声明+初始化
let userName = '张三';
console.log(userName); // 输出:张三
// 修改变量
userName = '李四';
console.log(userName); // 输出:李四
// 先声明,后初始化
let age;
age = 20;
console.log(age); // 输出:20
let 和 var 的核心区别,是作用域的不同(我们后面讲作用域的时候会详细拆解),这里你只要记住一个结论:现在写 JavaScript 代码,优先用 let 声明变量,不要用 var。
因为 let 修复了 var 的很多设计缺陷,比如变量提升、重复声明、块级作用域的问题,让代码更规范、更不容易出 bug。我们后面讲作用域的时候,会把两者的区别拆得明明白白,现在你只要会用 let 声明变量就行。
二、动态类型:JavaScript 变量的 "百变特性",彻底讲透
JavaScript 是一门动态类型语言,这是它和 C、C++、Java 这些静态类型语言最核心的区别之一。很多新手学不好 JS,就是因为没把动态类型的特性搞懂,我们用最通俗的话,把它讲得透透彻彻。
2.1 动态类型的两个核心特性
我们先把动态类型的两个核心特性讲清楚,再用例子和类比,让你彻底理解。
特性 1:变量的类型,是在程序运行过程中才确定的
在静态类型语言(比如 C++)里,你声明变量的时候,必须先告诉编译器 "这个变量是什么类型",比如 int a = 10;,你必须先写 int,告诉编译器这个变量是整数类型,编译的时候就确定了类型,运行的时候不能改。
但在 JavaScript 里,你声明变量的时候,完全不用告诉引擎 "这个变量是什么类型",引擎会在运行到赋值语句的时候,根据你赋的值,自动确定这个变量的类型。
举个例子:
// 只声明,不赋值,引擎根本不知道这个变量是什么类型
var a;
console.log(typeof a); // 输出:undefined(未定义类型)
// 运行到这行赋值语句,引擎才确定:a是number数字类型
a = 10;
console.log(typeof a); // 输出:number
// 运行到这行赋值语句,引擎又确定:a现在是string字符串类型
a = '张三';
console.log(typeof a); // 输出:string
就像我们的收纳盒类比:静态类型语言的收纳盒,你买的时候就定死了 "这个盒子只能装水",不能装别的;而 JavaScript 的收纳盒,是万能盒子,你放进去什么东西,它就是什么类型的盒子,放数字就是数字类型,放字符串就是字符串类型。
特性 2:随着程序运行,变量的类型可以随时改变
这是动态类型最核心的特点:同一个变量,你可以先给它赋一个数字,再给它赋一个字符串,再赋一个布尔值,它的类型会跟着你赋的值,随时发生改变。
举个例子:
// 声明变量,赋值为数字,类型是number
var a = 10;
console.log(a); // 输出:10
console.log(typeof a); // 输出:number
// 赋值为字符串,类型变成string
a = 'hello';
console.log(a); // 输出:hello
console.log(typeof a); // 输出:string
// 赋值为布尔值,类型变成boolean
a = true;
console.log(a); // 输出:true
console.log(typeof a); // 输出:boolean
// 赋值为null,类型变成object(历史遗留bug)
a = null;
console.log(a); // 输出:null
console.log(typeof a); // 输出:object
这种操作,在 C++、Java 这些静态类型语言里,是绝对不可能的 ------ 你声明了 int a = 10;,就永远不能给 a 赋一个字符串,编译器会直接报错。但在 JavaScript 里,这是完全合法的操作。
2.2 typeof 运算符:检测变量的当前类型
既然变量的类型可以随时变,那我们怎么知道一个变量当前是什么类型呢?用 typeof 运算符。
typeof 运算符的作用,就是检测一个变量 / 值的类型,返回一个小写的字符串,告诉你这个值的类型是什么。
语法(两种写法都可以,效果完全一样):
typeof 变量名;
typeof(变量名);
不同类型的值,typeof 的返回结果,我们列一个全的表,每个都讲清楚:
| 变量的值 | typeof 返回的字符串 | 说明 |
|---|---|---|
| 10、3.14、Infinity、NaN | 'number' |
所有数字、特殊数字值,都是 number 类型 |
| ' 张三 '、"hello"、'' | 'string' |
所有字符串,都是 string 类型 |
| true、false | 'boolean' |
布尔值,都是 boolean 类型 |
| 只声明不赋值的变量 | 'undefined' |
未定义的值,是 undefined 类型 |
| null | 'object' |
这是 JavaScript 的历史遗留 bug!null 本身是 null 类型,但 typeof 会返回 'object',从 JS 诞生以来就一直没修,因为要兼容旧代码 |
举个完整的例子:
// 数字类型
var num = 10;
console.log(typeof num); // 输出:'number'
// 字符串类型
var str = 'hello';
console.log(typeof str); // 输出:'string'
// 布尔类型
var bool = true;
console.log(typeof bool); // 输出:'boolean'
// 未定义类型
var und;
console.log(typeof und); // 输出:'undefined'
// null类型
var nul = null;
console.log(typeof nul); // 输出:'object'(历史bug)
2.3 动态类型的优缺点:灵活的代价
动态类型给 JavaScript 带来了极高的灵活性,写代码的时候不用纠结类型,开发速度非常快,但也带来了非常多的坑,我们客观地讲清楚。
优点:极致的灵活性,开发速度快
动态类型让你不用提前规划变量的类型,想赋什么值就赋什么值,写代码的时候不用被类型束缚,尤其是写小功能、小脚本的时候,速度非常快,不用写一堆类型声明的代码。
缺点:极其容易出 bug,大型项目难维护
灵活的代价,就是极其容易出类型相关的 bug。最经典的例子,就是我们之前提到的:用户输入两个数字,想做加法,结果变成了字符串拼接。
经典坑示例:
// 让用户输入两个数字
var num1 = prompt('请输入第一个数字:'); // 用户输入10,num1是字符串'10',不是数字10
var num2 = prompt('请输入第二个数字:'); // 用户输入20,num2是字符串'20',不是数字20
// 你想做加法,结果变成了字符串拼接
var sum = num1 + num2;
alert('和是:' + sum); // 弹出:和是:1020,而不是你想要的30
这个 bug 的根源,就是动态类型:prompt 返回的永远是字符串,你以为是数字,结果做加法的时候变成了拼接。在静态类型语言里,这种错误在编译的时候就会被发现,根本不会让程序运行;但在 JS 里,它不会报错,只会给你一个错误的结果,新手根本找不到问题出在哪里。
给新手的核心建议 :虽然 JS 允许你随便改变变量的类型,但在实际开发中,绝对不要随便改变一个变量的类型。一个变量,一开始赋值为数字,就一直让它存数字;一开始赋值为字符串,就一直让它存字符串。保持变量类型的一致性,能帮你避开 90% 的类型相关 bug。
三、基本数据类型:JavaScript 的 "数据积木",每个类型拆到极致
JavaScript 里的数据,分为两大类:基本数据类型(也叫原始类型) 和 引用数据类型(也叫复杂类型)。我们这篇内容,只讲基础语法里的 6 种基本数据类型,引用类型(对象、数组、函数)我们后面单独拆开来,一个一个讲透。
6 种基本数据类型是:number、string、boolean、undefined、null、Symbol(ES6 新增,入门阶段暂时不用管,我们重点讲前 5 种)。
3.1 number 数字类型:JS 里所有的数字,都是一家人
在 C++、Java 这些语言里,数字会分成很多种类型:int(整数)、long(长整数)、float(单精度浮点数)、double(双精度浮点数)、short(短整数)等等,不同的类型有不同的存储范围和精度。
但在 JavaScript 里,所有的数字,不管是整数、小数、正数、负数,都统一用 number 类型来表示,没有单独的整数类型和小数类型。
这是 JS 数字类型最核心的特点,我们先把这个点刻在脑子里,再讲其他细节。
3.1.1 number 类型的基础写法
number 类型的写法非常多,我们把所有常见的写法,一个一个讲清楚。
1. 整数写法
最基础的整数,直接写数字就行,不用加任何引号:
var num1 = 10; // 正整数
var num2 = -20; // 负整数
var num3 = 0; // 零
2. 小数写法(浮点数)
带小数点的数字,直接写就行,JS 会自动识别为小数:
var price1 = 3.14; // 正小数
var price2 = -0.5; // 负小数
var price3 = 10.0; // 虽然写了小数点,但会被JS自动转换成整数10
这里要注意:如果小数的整数部分是 0,可以省略 0,直接写 .5,代表 0.5,但非常不推荐这么写,可读性太差,很容易看错:
var num = .5; // 合法,但不推荐,等价于0.5
3. 科学计数法写法
如果数字特别大,或者特别小,我们可以用科学计数法来写,格式是:数字e+指数 或者 数字e-指数,代表 "数字乘以 10 的指数次方"。
举个例子:
// 特别大的数字
var bigNum = 1.23e5; // 等价于 1.23 * 10^5 = 123000
var maxNum = 1.79e308; // JS能表示的最大数字
// 特别小的数字
var smallNum = 1.23e-4; // 等价于 1.23 * 10^-4 = 0.000123
var minNum = 5e-324; // JS能表示的最小正数字
3.1.2 数字的进制表示:二进制、八进制、十进制、十六进制
我们平时生活里用的是十进制 (满 10 进 1),但计算机底层用的是二进制 (满 2 进 1),为了方便写二进制数字,JS 还支持八进制 和十六进制的写法。
什么是进制?
进制,就是 "满多少进 1" 的计数规则。
- 十进制:我们平时用的,有 0-9 十个数字,满 10 进 1,10 个 1 变成 1 个 10,10 个 10 变成 1 个 100;
- 二进制:计算机用的,只有 0 和 1 两个数字,满 2 进 1,2 个 1 变成 1 个 10(二进制的 10,等于十进制的 2);
- 八进制:有 0-7 八个数字,满 8 进 1,8 个 1 变成 1 个 10(八进制的 10,等于十进制的 8);
- 十六进制:有 0-9、A-F(a-f)十六个数字,A 代表 10,B 代表 11,...,F 代表 15,满 16 进 1,16 个 1 变成 1 个 10(十六进制的 10,等于十进制的 16)。
为什么要有八进制和十六进制? 因为二进制写起来太长了,比如十进制的 10,二进制要写成1010(4 位),十进制的 100,二进制要写成1100100(7 位),写起来非常麻烦,很容易看错。
而:
- 1 个八进制数字,正好对应 3 个二进制数字;
- 1 个十六进制数字,正好对应 4 个二进制数字。
用八进制或十六进制,就能把很长的二进制数字,写得很短,比如二进制的1100100,十六进制写成64,一下子就短了很多,看起来也方便。
JS 里不同进制的写法
我们把每种进制的写法、规则、例子,都讲得明明白白。
1. 十进制:默认写法,不用加任何前缀
我们平时写的数字,默认都是十进制,不用加任何前缀:
var num = 10; // 十进制的10
2. 二进制:以 0b 或 0B 开头
二进制数字,必须以 0b 或 0B 开头,后面只能跟 0 和 1,否则会报错:
var bin1 = 0b1010; // 二进制的1010,等于十进制的10
var bin2 = 0B111; // 二进制的111,等于十进制的7
console.log(bin1); // 输出:10(浏览器会自动转成十进制显示)
console.log(bin2); // 输出:7
3. 八进制:以 0o 或 0O 开头(推荐),或以 0 开头(旧写法,不推荐)
八进制有两种写法:
- 推荐写法 :以
0o或0O开头,后面只能跟 0-7 的数字,否则报错。这是 ES6 之后的标准写法,不会和十进制混淆; - 旧写法 :以
0开头,后面跟 0-7 的数字。这种写法非常不推荐,因为很容易和十进制混淆,比如你想写十进制的 012,结果 JS 会当成八进制的 12,等于十进制的 10,很容易出 bug。
例子:
// 推荐写法
var oct1 = 0o12; // 八进制的12,等于十进制的10
var oct2 = 0O77; // 八进制的77,等于十进制的63
console.log(oct1); // 输出:10
console.log(oct2); // 输出:63
// 旧写法(不推荐)
var octOld = 012; // 八进制的12,等于十进制的10,很容易误以为是十进制的12
4. 十六进制:以 0x 或 0X 开头
十六进制数字,必须以 0x 或 0X 开头,后面可以跟 0-9、A-F(大小写都可以),A 代表 10,B 代表 11,...,F 代表 15:
var hex1 = 0xA; // 十六进制的A,等于十进制的10
var hex2 = 0x1F; // 十六进制的1F,等于十进制的31(1*16 + 15 = 31)
var hex3 = 0XFF; // 十六进制的FF,等于十进制的255(15*16 +15 =255)
console.log(hex1); // 输出:10
console.log(hex2); // 输出:31
console.log(hex3); // 输出:255
十六进制在前端里非常常用,比如颜色值 #FFFFFF 就是十六进制的写法,代表白色。
3.1.3 number 类型的三个特殊值:Infinity、-Infinity、NaN
number 类型里,有三个非常特殊的值,它们虽然是 "数字类型",但和普通的数字不一样,我们一个一个讲透。
1. Infinity:正无穷大
Infinity 表示 "正无穷大",它是一个比任何数字都大的值。
什么时候会得到 Infinity? 当你的数字超过了 JavaScript 能表示的最大范围,就会变成 Infinity。JS 能表示的最大数字,可以用 Number.MAX_VALUE 来获取,它的值大约是 1.7976931348623157e+308,超过这个值,就会变成 Infinity。
举个例子:
var maxNum = Number.MAX_VALUE;
console.log(maxNum); // 输出:1.7976931348623157e+308
// 最大数字乘以2,超过了范围,变成Infinity
console.log(maxNum * 2); // 输出:Infinity
// 1除以0,也会得到Infinity(注意:JS里1/0不会报错,会返回Infinity)
console.log(1 / 0); // 输出:Infinity
2. -Infinity:负无穷大
-Infinity 表示 "负无穷大",它是一个比任何数字都小的值。
**什么时候会得到 -Infinity?**当你的数字超过了 JavaScript 能表示的最小负数值,就会变成 -Infinity。
举个例子:
var minNum = -Number.MAX_VALUE;
console.log(minNum); // 输出:-1.7976931348623157e+308
// 最小负数乘以2,超过范围,变成-Infinity
console.log(minNum * 2); // 输出:-Infinity
// -1除以0,会得到-Infinity
console.log(-1 / 0); // 输出:-Infinity
重要区分:负无穷大 ≠ 无穷小
很多新手会把这两个概念搞混,我们必须彻底讲清楚:
- 负无穷大(-Infinity) :是一个比任何数字都小的数,比如
-1.79e308 * 2,它无限趋近于 "负的无限远"; - 无穷小 :是一个无限趋近于 0 的数,值为
1 / Infinity,结果是 0,它无限趋近于 0。
这两个东西完全不是一回事,绝对不能搞混!
3. NaN:Not a Number(不是一个数字)
NaN 是 number 类型里最特殊的一个值,它的全称是 "Not a Number",翻译过来是 "不是一个数字",但它的类型却是 number(用 typeof 检测会返回 'number'),是不是很矛盾?
我们先讲清楚:**什么时候会得到 NaN?**当你做一个数学运算,但这个运算的结果 "不是一个合法的数字",就会得到 NaN。
所有常见的得到 NaN 的场景:
// 1. 字符串减数字、乘数字、除数字(注意:加数字会变成拼接,不会得到NaN)
console.log('hello' - 10); // 输出:NaN
console.log('hello' * 10); // 输出:NaN
console.log('hello' / 10); // 输出:NaN
// 2. 0除以0
console.log(0 / 0); // 输出:NaN
// 3. 负数开平方
console.log(Math.sqrt(-1)); // 输出:NaN(Math.sqrt是开平方的函数)
// 4. Infinity 减 Infinity
console.log(Infinity - Infinity); // 输出:NaN
// 5. 字符串转数字失败
console.log(Number('hello')); // 输出:NaN(把'hello'转成数字,失败了,返回NaN)
NaN 的两个核心特性,必须刻在脑子里:
-
NaN 和任何值都不相等,包括它自己!
console.log(NaN == NaN); // 输出:false console.log(NaN === NaN); // 输出:false这是 JS 里唯一一个自己不等于自己的值,非常特殊。
-
NaN 和任何值做任何数学运算,结果都是 NaN!
console.log(NaN + 10); // 输出:NaN console.log(NaN - 10); // 输出:NaN console.log(NaN * 10); // 输出:NaN console.log(NaN / 10); // 输出:NaN
isNaN () 函数:判断一个值是不是 NaN
因为 NaN 不等于自己,所以我们不能用 == 或 === 来判断一个值是不是 NaN,必须用 JS 提供的 isNaN () 函数。
isNaN () 函数的作用 :它会先尝试把你传进去的值,转换成数字,如果转换后的结果是 NaN,就返回 true;如果转换后是合法的数字,就返回 false。
举个例子:
// 合法的数字,返回false
console.log(isNaN(10)); // 输出:false
console.log(isNaN(3.14)); // 输出:false
console.log(isNaN(Infinity)); // 输出:false(Infinity是合法的数字类型)
// 能转换成数字的字符串,返回false
console.log(isNaN('123')); // 输出:false('123'能转成数字123)
console.log(isNaN('')); // 输出:false(空字符串能转成数字0)
console.log(isNaN(' ')); // 输出:false(空格字符串能转成数字0)
// 不能转换成数字的,返回true
console.log(isNaN('hello')); // 输出:true('hello'转不成数字)
console.log(isNaN(NaN)); // 输出:true(NaN本身就是非数字)
console.log(isNaN('hello' - 10)); // 输出:true
这里要注意一个坑:isNaN('') 会返回 false,因为空字符串会被转换成数字 0,很多新手会在这里踩坑,一定要注意。
3.1.4 经典坑:0.1 + 0.2 不等于 0.3
这是 JavaScript 里最经典的一个坑,也是所有使用 IEEE 754 浮点数标准的语言都会有的坑:0.1 + 0.2 的结果,不是 0.3,而是 0.30000000000000004。
我们先验证一下:
console.log(0.1 + 0.2); // 输出:0.30000000000000004
console.log(0.1 + 0.2 === 0.3); // 输出:false
为什么会这样? 我们用最通俗的话讲清楚背后的原理:计算机底层,所有的数字都是用二进制存储的。我们的十进制小数,比如 0.1,转换成二进制的时候,会变成一个无限循环的二进制小数,就像我们十进制里,没法精确表示 1/3,只能写成 0.333333... 无限循环一样。
而 JavaScript 的 number 类型,是用 64 位双精度浮点数存储的,它的存储空间是有限的,只能存下无限循环小数的前几十位,后面的就被截断了,所以存储的 0.1 和 0.2,本身就不是精确的 0.1 和 0.2,而是一个近似值。两个近似值加起来,自然就不是精确的 0.3 了。
怎么解决这个问题? 最常用的解决方法,就是把小数转换成整数来运算,运算完之后再转回小数。比如 0.1 + 0.2,我们可以先把两个数都乘以 10,变成 1 + 2 = 3,再除以 10,得到 0.3,就不会有精度问题了。
代码示例:
var num1 = 0.1;
var num2 = 0.2;
// 先乘以10转成整数,相加,再除以10
var sum = (num1 * 10 + num2 * 10) / 10;
console.log(sum); // 输出:0.3
console.log(sum === 0.3); // 输出:true
如果是更多位的小数,比如 0.01 + 0.02,就乘以 100;0.001 + 0.002,就乘以 1000,以此类推。
另外,也可以用 toFixed() 方法,保留指定的小数位数,这个方法我们后面讲字符串的时候会详细讲。
3.2 string 字符串类型:JS 里的文本,每个细节都讲透
字符串,就是 JavaScript 里用来表示 "文本" 的类型,比如名字、地址、一句话、一段文字,都是用字符串类型来存储的。
我们把字符串的所有知识点,从基础写法到高级特性,一个一个拆透。
3.2.1 字符串的基础写法:单引号、双引号都可以
在 JavaScript 里,字符串必须用英文单引号 '' 或者 英文双引号 "" 包裹起来,两者没有任何区别,你喜欢用哪个就用哪个,行业里一般推荐用单引号。
基础示例:
// 单引号写法(推荐)
var str1 = 'hello world';
var str2 = '张三';
// 双引号写法
var str3 = "hello world";
var str4 = "李四";
// 空字符串:引号里什么都不写
var emptyStr = '';
新手最容易犯的错误:
- 字符串不加引号:
var str = hello;,引擎会以为hello是一个变量名,报 "未定义" 的错误; - 用中文引号:
var str = "hello";,直接报语法错误; - 引号不配对:
var str = 'hello;,只写了开头的引号,没写结尾的引号,直接报语法错误。
3.2.2 字符串里包含引号:两种解决方法
很多新手会遇到一个问题:我想在字符串里写引号,怎么办?比如我想表示 我的名字是"张三",如果直接写 var str = "我的名字是"张三"";,引擎会以为前两个引号是一对,后面的 "张三"" 是多余的,直接报错。
这里有两种完美的解决方法,我们一个一个讲透。
方法 1:单双引号搭配使用(最简单、最推荐)
如果你的字符串里要写双引号,那外层就用单引号包裹;如果字符串里要写单引号,外层就用双引号包裹。这样完全不用转义,代码最简洁,可读性最高。
举个例子:
// 字符串里有双引号,外层用单引号
var str1 = '我的名字是"张三"';
console.log(str1); // 输出:我的名字是"张三"
// 字符串里有单引号,外层用双引号
var str2 = "我是'张三',今年20岁";
console.log(str2); // 输出:我是'张三',今年20岁
方法 2:用转义字符 \ 转义引号
如果你的字符串里既有单引号,又有双引号,或者你不想换外层的引号,就可以用转义字符 \(反斜杠),在你要写的引号前面加一个 \,告诉 JS 引擎:"这个引号不是字符串的结束,是字符串里的内容,不要解析它"。
举个例子:
// 用\"转义双引号
var str1 = "我的名字是\"张三\"";
console.log(str1); // 输出:我的名字是"张三"
// 用\'转义单引号
var str2 = '我是\'张三\',今年20岁';
console.log(str2); // 输出:我是'张三',今年20岁
// 既有单引号又有双引号,用转义字符
var str3 = '他说:"我叫\'张三\'"';
console.log(str3); // 输出:他说:"我叫'张三'"
3.2.3 常用的转义字符
转义字符 \ 除了能转义引号,还能用来表示一些 "不方便直接输入的特殊字符",我们把最常用的转义字符,一个一个讲清楚,每个都给例子。
| 转义字符 | 含义 | 示例和效果 |
|---|---|---|
\n |
换行符 | '第一行\n第二行',输出的时候会分成两行显示 |
\t |
制表符(相当于按一下 Tab 键) | '姓名\t年龄\t分数',输出的时候中间会有一个大空格,用来对齐内容 |
\\ |
反斜杠本身 | 因为 \ 本身是转义字符,所以你要表示一个反斜杠,必须写两个 \\ |
\' |
单引号 | 转义单引号,让它变成字符串的内容 |
\" |
双引号 | 转义双引号,让它变成字符串的内容 |
每个转义字符的详细示例:
-
\n换行符var str = '第一行\n第二行\n第三行'; console.log(str); // 输出: // 第一行 // 第二行 // 第三行这个换行符,在
alert()弹框里,和console.log()控制台里,都能生效,用来换行显示内容,非常常用。 -
\t制表符var str = '姓名\t年龄\t分数\n张三\t20\t90\n李四\t21\t85'; console.log(str); // 输出: // 姓名 年龄 分数 // 张三 20 90 // 李四 21 85制表符可以让内容按列对齐,看起来非常整齐,在控制台打印表格的时候非常好用。
-
\\反斜杠本身// 表示Windows文件路径:C:\Users\Desktop\test.txt var path = 'C:\\Users\\Desktop\\test.txt'; console.log(path); // 输出:C:\Users\Desktop\test.txt如果你只写一个
\,引擎会以为你要转义后面的字符,所以必须写两个\\,才能表示一个反斜杠本身。
3.2.4 字符串的 length 属性:获取字符串的长度
我们经常需要知道一个字符串里有多少个字符,这时候就可以用字符串的 length 属性。
语法:
字符串变量名.length;
length 属性会返回一个数字,代表这个字符串里有多少个字符。注意:是字符的数量,不是字节的数量,一个中文汉字、一个英文字母、一个数字、一个空格,都算一个字符。
举个例子:
// 英文字符串
var str1 = 'hello';
console.log(str1.length); // 输出:5(h、e、l、l、o,5个字符)
// 中文字符串
var str2 = '你好世界';
console.log(str2.length); // 输出:4(每个中文算一个字符)
// 带空格的字符串
var str3 = 'hello world';
console.log(str3.length); // 输出:11(中间的空格也算一个字符)
// 空字符串
var str4 = '';
console.log(str4.length); // 输出:0(空字符串长度是0)
// 全是空格的字符串
var str5 = ' ';
console.log(str5.length); // 输出:3(3个空格,每个算一个字符)
这里要注意一个新手容易踩的坑:空字符串 '' 和全是空格的字符串 ' ' 是完全不一样的。空字符串的 length 是 0,而全是空格的字符串,length 是空格的数量,很多新手会以为空格不算字符,其实是算的。
3.2.5 字符串拼接:用 + 号,彻底讲透
字符串拼接,就是把两个或多个字符串,首尾接在一起,变成一个新的字符串。在 JavaScript 里,字符串拼接用 + 号,这是最基础、最常用的字符串操作。
1. 纯字符串之间的拼接
如果 + 号的两边都是字符串,就会直接把两个字符串首尾接在一起,生成一个新的字符串。
举个例子:
var str1 = '我的名字是';
var str2 = '张三';
var result = str1 + str2;
console.log(result); // 输出:我的名字是张三
// 多个字符串拼接
var str3 = '我今年';
var str4 = '20';
var str5 = '岁';
var result2 = str3 + str4 + str5;
console.log(result2); // 输出:我今年20岁
2. 字符串和其他类型的拼接:隐式类型转换
这是 + 号最核心的特性,也是新手最容易踩坑的地方:如果 + 号的两边,有一边是字符串,另一边是其他类型的值(数字、布尔值、null、undefined),JS 会自动把另一边的值,转换成字符串,然后再做拼接。
简单说就是:只要 + 号有一边是字符串,就一定会做字符串拼接,而不是数学加法。
我们用大量的例子,把所有情况都讲透:
// 1. 字符串 + 数字:数字转成字符串,再拼接
console.log('100' + 100); // 输出:'100100'(不是200!是字符串拼接)
console.log(100 + '100'); // 输出:'100100'(不管字符串在左边还是右边,都是拼接)
console.log('年龄:' + 20); // 输出:'年龄:20'
// 2. 字符串 + 布尔值:布尔值转成字符串'true'或'false',再拼接
console.log('是否成年:' + true); // 输出:'是否成年:true'
console.log('是否学生:' + false); // 输出:'是否学生:false'
// 3. 字符串 + null/undefined:转成字符串'null'/'undefined',再拼接
console.log('值:' + null); // 输出:'值:null'
console.log('值:' + undefined); // 输出:'值:undefined'
3. 多个 + 号的运算顺序:从左到右
如果有多个 + 号连在一起,JS 会从左到右依次运算,前面的运算结果,会和后面的继续运算。
这是新手最容易踩坑的地方,我们用例子讲透:
// 例子1:先算数字加法,再算字符串拼接
console.log(100 + 100 + '100');
// 运算顺序:
// 第一步:100 + 100 = 200(两边都是数字,做加法)
// 第二步:200 + '100' = '200100'(一边是数字,一边是字符串,做拼接)
// 最终输出:'200100'
// 例子2:先算字符串拼接,后面的全是拼接
console.log('100' + 100 + 100);
// 运算顺序:
// 第一步:'100' + 100 = '100100'(字符串拼接)
// 第二步:'100100' + 100 = '100100100'(还是字符串拼接)
// 最终输出:'100100100'
// 例子3:用括号改变运算顺序
console.log('和是:' + (100 + 100));
// 运算顺序:
// 第一步:先算括号里的 100 + 100 = 200
// 第二步:'和是:' + 200 = '和是:200'
// 最终输出:'和是:200'
这个例子,完美解决了我们之前提到的 "用户输入两个数字,加法变成拼接" 的坑。如果你想让两个数字先做加法,再和字符串拼接,就必须用括号把加法括起来,让它先运算。
解决用户输入数字相加的坑:
var num1 = prompt('请输入第一个数字:');
var num2 = prompt('请输入第二个数字:');
// 错误写法:先拼接,再转数字
var sumWrong = num1 + num2;
alert('错误的结果:' + sumWrong); // 输出1020
// 正确写法1:用Number转成数字,再加法
var sumRight1 = Number(num1) + Number(num2);
alert('正确的结果1:' + sumRight1); // 输出30
// 正确写法2:用括号,先转数字再加法,再拼接
alert('正确的结果2:' + (Number(num1) + Number(num2))); // 输出30
3.3 boolean 布尔类型:只有两个值的 "开关类型"
布尔类型是 JavaScript 里最简单的类型,它只有两个值:true(真)和 false(假),没有其他任何值。
3.3.1 布尔类型的本质:"是" 与 "否" 的判断
布尔类型的核心作用,就是用来表示 "逻辑判断的结果"------ 一件事,要么是 "真的、成立的、对的",要么是 "假的、不成立的、错的",没有中间状态。
我们用生活里的例子理解:
- "你是不是成年人?" 答案只有 "是(true)" 或 "否(false)";
- "10 是不是大于 5?" 答案只有 "是(true)" 或 "否(false)";
- "你有没有登录?" 答案只有 "是(true)" 或 "否(false)"。
所有的条件判断,最终都会得到一个布尔值,布尔类型就是 JS 里所有逻辑判断的基础,没有布尔类型,代码就没法做选择、没法做循环。
3.3.2 布尔类型的基础写法
布尔值只有 true 和 false 两个,直接写就行,不用加任何引号,而且必须全小写,不能写成 True 或 FALSE,否则会被当成变量名,报未定义的错误。
正确写法:
var isAdult = true; // 是成年人
var isStudent = false; // 不是学生
console.log(isAdult); // 输出:true
console.log(isStudent); // 输出:false
console.log(typeof isAdult); // 输出:'boolean'
错误写法:
var bool1 = True; // 错误!首字母大写,引擎会以为是变量名,报错
var bool2 = 'true'; // 错误!加了引号,变成了字符串,不是布尔类型
3.3.3 布尔值的隐式类型转换:true 转 1,false 转 0
当布尔值和数字一起做数学运算的时候,JS 会自动把布尔值转换成数字:true 会转换成 1,false 会转换成 0。
举个例子:
console.log(true + 1); // 输出:2(true转成1,1+1=2)
console.log(false + 1); // 输出:1(false转成0,0+1=1)
console.log(true * 10); // 输出:10(true转成1,1*10=10)
console.log(false * 10); // 输出:0(false转成0,0*10=0)
重要提醒 :虽然 JS 支持布尔值做数学运算,但实际开发中,绝对不要这么写!
为什么?因为布尔值的作用是做逻辑判断,不是做数学运算。你写 true + 1,别人看你的代码,根本不知道你要干嘛,代码的可读性极差,而且非常容易出 bug。布尔值就应该只用在逻辑判断里,不要用来做数学运算,这是行业里的通用规范。
3.4 undefined 未定义类型:只有一个值的类型
undefined 类型,是 JS 里最特殊的类型之一,它只有一个值,就是 undefined。
3.4.1 什么时候会得到 undefined?
undefined 表示 "未定义"------ 一个变量被声明了,但没有被赋值,它的默认值就是 undefined。
所有常见的得到 undefined 的场景:
-
只声明变量,不赋值,默认值就是 undefined
var a; // 只声明,不赋值 console.log(a); // 输出:undefined console.log(typeof a); // 输出:'undefined' -
访问对象不存在的属性,返回 undefined (后面讲对象的时候会详细说)
var user = { name: '张三' }; console.log(user.age); // 输出:undefined(user对象里没有age属性) -
函数没有返回值,默认返回 undefined (后面讲函数的时候会说)
function sayHello() { console.log('hello'); } var result = sayHello(); console.log(result); // 输出:undefined(函数没有return返回值) -
调用函数时,参数没传,这个参数的值就是 undefined
function sayHi(name) { console.log(name); } sayHi(); // 调用函数时没传name参数 // 输出:undefined
其中,最基础、最常见的场景,就是第一种:只声明变量,不赋值,默认值就是 undefined。
3.4.2 undefined 和其他类型运算的结果
我们把 undefined 和其他类型做运算的结果,全部讲清楚,避免踩坑:
-
undefined 和字符串拼接 :会把 undefined 转成字符串
'undefined',然后做拼接var a; console.log(a + '10'); // 输出:'undefined10' -
undefined 和数字做数学运算 :会把 undefined 转成数字
NaN,所以运算结果永远是 NaNvar a; console.log(a + 10); // 输出:NaN console.log(a - 10); // 输出:NaN console.log(a * 10); // 输出:NaN -
undefined 做布尔转换 :会转成
false(后面讲条件语句的时候会说)var a; if (a) { console.log('会执行吗?'); // 不会执行,因为a是undefined,转成false }
3.5 null 空值类型:表示 "空的盒子"
null 类型,也是只有一个值的类型,它只有一个值 null,表示 "空值"。
3.5.1 null 的含义:主动赋值的 "空"
很多新手会把 null 和 undefined 搞混,我们用最通俗的话,讲清楚两者的核心区别:
- undefined:相当于 "你连收纳盒都没准备好"------ 你只说了要一个盒子,但盒子还没拿出来,里面自然什么都没有,是 JS 引擎自动给你的默认值;
- null:相当于 "你有一个收纳盒,盒子是空的,里面什么都没放"------ 你主动给这个变量赋值为 "空",表示这个变量未来会存一个值,但现在暂时是空的。
简单说:undefined 是被动的、默认的空;null 是主动的、人为的空。
null 的使用场景 :当你声明一个变量,未来要用来存一个对象 / 数组 / 复杂数据,但现在还没有这个数据,就可以先给它赋值为 null,告诉自己和其他开发者:"这个变量未来会存一个值,现在暂时是空的"。
举个例子:
// 未来要存用户的信息,现在还没获取到,先赋值为null
var userInfo = null;
console.log(userInfo); // 输出:null
console.log(typeof userInfo); // 输出:'object'(历史遗留bug)
// 后面获取到用户信息了,再给它赋值
userInfo = { name: '张三', age: 20 };
3.5.2 null 和其他类型运算的结果
我们把 null 和其他类型运算的结果,全部讲清楚,并且和 undefined 做对比:
-
null 和字符串拼接 :会把 null 转成字符串
'null',然后做拼接var b = null; console.log(b + '10'); // 输出:'null10' -
null 和数字做数学运算 :会把 null 转成数字
0,然后做运算(这是和 undefined 最大的区别!)var b = null; console.log(b + 10); // 输出:10(null转成0,0+10=10) console.log(b - 10); // 输出:-10(0-10=-10) console.log(b * 10); // 输出:0(0*10=0) -
null 做布尔转换 :会转成
false(和 undefined 一样)var b = null; if (b) { console.log('会执行吗?'); // 不会执行,null转成false }
3.5.3 null 和 undefined 的相等性判断
这里有一个 JS 的特殊规则,必须记住:
null == undefined会返回true(抽象相等,会做隐式转换);null === undefined会返回false(严格相等,类型不同,一个是 null 类型,一个是 undefined 类型)。
举个例子:
console.log(null == undefined); // 输出:true
console.log(null === undefined); // 输出:false
这个规则是 JS 诞生的时候就定下来的,不用纠结为什么,记住就行。实际开发中,我们一般用 ===,所以两者是不相等的。
3.5.4 为什么 typeof null 会返回 'object'?
很多新手会问:null 是 null 类型,为什么 typeof null 会返回 'object'?
这是 JavaScript 诞生的时候就存在的一个历史遗留 bug。在 JS 最初的版本里,值的存储是用 "类型标签 + 值" 的形式,对象的类型标签是 0,而 null 的值是全 0,所以它的类型标签被当成了 0,typeof 就把它当成了 object。
这个 bug 从 JS 诞生以来,一直没有被修复,因为修复它会导致大量的旧代码报错,为了兼容旧代码,就一直保留了下来。我们只要知道这个 bug 就行,不用纠结,实际开发中,判断一个值是不是 null,直接用 === null 就行。
四、运算符:JS 里处理数据的 "工具",每个运算符拆到极致
运算符,就是 JS 里用来处理数据的 "工具",比如用 + 做加法、用 == 做比较、用 && 做逻辑判断。我们把 JS 里所有基础的运算符,一个一个讲透,每个都讲清 "是什么、怎么用、有什么坑、什么时候用",没有任何一笔带过的内容。
4.1 算术运算符:做数学运算的工具
算术运算符,就是用来做数学加减乘除运算的,是最基础的运算符,我们一个一个讲。
| 运算符 | 含义 | 示例 | 结果 |
|---|---|---|---|
+ |
加法(或字符串拼接) | 10 + 20 |
30 |
- |
减法 | 20 - 10 |
10 |
* |
乘法 | 10 * 20 |
200 |
/ |
除法 | 20 / 10 |
2 |
% |
取余数(取模) | 20 % 3 |
2 |
4.1.1 加法运算符 +
我们前面讲字符串拼接的时候,已经把 + 号讲得很透了,这里再总结一下它的两个核心作用:
- 两边都是数字:做数学加法;
- 至少有一边是字符串:做字符串拼接,另一边会自动转成字符串。
这里再补充一个点:+ 号如果只写在一个数字前面,会把这个值强制转换成数字,比如 +'123' 会得到数字 123,这是一个快速把字符串转成数字的小技巧。
举个例子:
var str = '123';
var num = +str; // 把字符串'123'转成数字123
console.log(num); // 输出:123
console.log(typeof num); // 输出:'number'
4.1.2 减法运算符 -
减法运算符,只有一个作用:做数学减法。它会把两边的值,都自动转换成数字,再做减法,如果转换失败,结果就是 NaN。
举个例子:
// 纯数字减法
console.log(20 - 10); // 输出:10
// 字符串数字减法:会自动转成数字
console.log('20' - '10'); // 输出:10(两边都转成数字,做减法)
// 非数字字符串减法:转不成数字,结果NaN
console.log('hello' - 10); // 输出:NaN
// 布尔值减法:true转1,false转0
console.log(true - 1); // 输出:0
console.log(10 - false); // 输出:10
这是减法和加法最大的区别:减法永远只会做数学运算,不会做字符串拼接,哪怕两边都是字符串,也会先转成数字再做减法。
4.1.3 乘法运算符 * 和除法运算符 /
乘法和除法,和减法一样,只会做数学运算,会把两边的值自动转成数字,转换失败就返回 NaN。
这里要重点讲一下除法:在 JS 里,除法不会做 "整数除法",哪怕两个整数能整除,结果也会是小数形式(虽然值和整数一样),如果不能整除,就会返回小数,这和 C++ 里的整数除法完全不一样,新手要注意。
举个例子:
// 乘法
console.log(10 * 20); // 输出:200
console.log('10' * '20'); // 输出:200(转成数字做乘法)
console.log('hello' * 10); // 输出:NaN
// 除法
console.log(20 / 10); // 输出:2(能整除,结果是2)
console.log(5 / 2); // 输出:2.5(不会舍去小数部分,和C++不一样)
console.log(10 / 3); // 输出:3.3333333333333335
console.log('20' / '5'); // 输出:4(转成数字做除法)
console.log('hello' / 2); // 输出:NaN
4.1.4 取余数运算符 %(取模)
取余数运算符,是新手最容易搞不懂的运算符,我们用最通俗的话讲透:% 号,就是求 "除法里除不尽剩下的余数"。
比如 20 除以 3,商是 6,余数是 2,所以 20 % 3 的结果就是 2。
举个基础例子:
console.log(20 % 3); // 输出:2(20除以3,商6余2)
console.log(10 % 2); // 输出:0(10除以2,商5余0)
console.log(7 % 5); // 输出:2(7除以5,商1余2)
console.log(5 % 7); // 输出:5(5除以7,商0余5)
负数的取余规则 :很多新手会问:负数的取余,结果是正还是负?JS 里的取余规则是:结果的符号,和被除数的符号一致。
举个例子:
console.log(-3 % 2); // 输出:-1(被除数是-3,结果是负的)
console.log(3 % -2); // 输出:1(被除数是3,结果是正的)
console.log(-7 % 3); // 输出:-1
console.log(7 % -3); // 输出:1
这就是我们之前说的:判断一个数是不是奇数,不能写 num % 2 == 1 ,因为负奇数的取余结果是 -1,-1 == 1 是 false,会判断错误。正确的写法是 num % 2 != 0,或者 num % 2 === 1 || num % 2 === -1。
正确的奇偶判断:
function isOdd(num) {
return num % 2 !== 0; // 余数不是0,就是奇数
}
console.log(isOdd(3)); // 输出:true
console.log(isOdd(-3)); // 输出:true(正确)
console.log(isOdd(4)); // 输出:false
取余运算符的常用场景:
- 判断一个数的奇偶性;
- 判断一个数是不是另一个数的倍数 (比如
num % 3 == 0,就是 3 的倍数); - 循环里的分页、隔行变色 (比如
i % 2 == 0,就是偶数行); - 限制数字的范围 (比如
num % 10,结果永远在 0-9 之间)。
4.2 赋值运算符 & 复合赋值运算符
赋值运算符,就是用来给变量赋值的,我们最基础的 = 就是赋值运算符,还有一些复合赋值运算符,是 "算术运算 + 赋值" 的结合,能让代码更简洁。
4.2.1 基础赋值运算符 =
我们前面已经讲过无数次了,= 号是 "赋值动作",把右边的值,放进左边的变量里,这里就不再重复了,只强调一点:赋值运算符的优先级是最低的,永远是先算右边的表达式,再把结果赋给左边的变量。
举个例子:
var a = 10 + 20 * 3;
// 先算右边的 20*3=60,再算10+60=70,最后把70赋给a
console.log(a); // 输出:70
4.2.2 复合赋值运算符
复合赋值运算符,就是把 "算术运算" 和 "赋值" 合并成一个运算符,让代码更简洁,不用写两遍变量名。
常用的复合赋值运算符表:
| 复合赋值运算符 | 等价于 | 示例 | 结果(初始 a=10) |
|---|---|---|---|
+= |
a = a + b |
a += 5 |
a = 15 |
-= |
a = a - b |
a -= 5 |
a = 5 |
*= |
a = a * b |
a *= 5 |
a = 50 |
/= |
a = a / b |
a /= 5 |
a = 2 |
%= |
a = a % b |
a %= 3 |
a = 1 |
举个详细的例子:
var a = 10;
// 等价于 a = a + 20;
a += 20;
console.log(a); // 输出:30
// 等价于 a = a - 10;
a -= 10;
console.log(a); // 输出:20
// 等价于 a = a * 2;
a *= 2;
console.log(a); // 输出:40
// 等价于 a = a / 4;
a /= 4;
console.log(a); // 输出:10
// 等价于 a = a % 3;
a %= 3;
console.log(a); // 输出:1
为什么要用复合赋值运算符?
- 代码更简洁 :不用写两遍变量名,尤其是变量名很长的时候,比如
userInfo.age += 1,比userInfo.age = userInfo.age + 1简洁太多了; - 性能更好:JS 引擎只需要查找一次变量,性能稍微好一点(虽然现在几乎可以忽略,但也是一个优点)。
4.3 自增自减运算符:让变量加 1 或减 1 的快捷方式
自增自减运算符,是专门用来 "让变量的值加 1" 或 "减 1" 的,在循环里用得非常多。
| 运算符 | 含义 |
|---|---|
++ |
自增 1,让变量的值加 1 |
-- |
自减 1,让变量的值减 1 |
自增自减运算符,有两种写法:前置写法 (++a、--a)和 后置写法 (a++、a--),两者的核心区别,是 "加 1 / 减 1 的时机不同",这是新手最容易搞混的地方,我们用最通俗的话讲透。
4.3.1 前置自增 / 自减:先加 / 减,后用
前置写法 :运算符写在变量前面,比如 ++a、--a。
它的执行顺序是:先把变量的值加 1 / 减 1,然后再使用变量的新值。
举个例子:
var a = 10;
var b = ++a; // 前置自增
// 执行顺序:
// 1. 先把a的值加1,a变成11
// 2. 再把a的新值11,赋给b
console.log(a); // 输出:11
console.log(b); // 输出:11
前置自减也是一样的逻辑:
var a = 10;
var b = --a; // 前置自减
// 执行顺序:
// 1. 先把a的值减1,a变成9
// 2. 再把a的新值9,赋给b
console.log(a); // 输出:9
console.log(b); // 输出:9
4.3.2 后置自增 / 自减:先用,后加 / 减
后置写法 :运算符写在变量后面,比如 a++、a--。
它的执行顺序是:先使用变量当前的旧值,然后再把变量的值加 1 / 减 1。
举个例子:
var a = 10;
var b = a++; // 后置自增
// 执行顺序:
// 1. 先把a当前的旧值10,赋给b
// 2. 再把a的值加1,a变成11
console.log(a); // 输出:11
console.log(b); // 输出:10
后置自减也是一样的逻辑:
var a = 10;
var b = a--; // 后置自减
// 执行顺序:
// 1. 先把a当前的旧值10,赋给b
// 2. 再把a的值减1,a变成9
console.log(a); // 输出:9
console.log(b); // 输出:10
4.3.3 复杂例子拆解,彻底搞懂区别
我们用一个稍微复杂的例子,一步步拆解执行顺序,让你彻底搞懂前置和后置的区别。
例子:
var a = 10;
var b = 5;
var c = a++ + ++b;
console.log(a); // ?
console.log(b); // ?
console.log(c); // ?
一步步拆解执行顺序:
- 先声明变量
a=10,b=5; - 计算
c = a++ + ++b,从左到右执行:- 第一步:计算
a++(后置自增):先使用 a 的旧值 10,然后 a 加 1 变成 11; - 第二步:计算
++b(前置自增):先把 b 加 1 变成 6,然后使用 b 的新值 6; - 第三步:把两个结果相加:10 + 6 = 16,赋给 c;
- 第一步:计算
- 最终结果:
- a = 11;
- b = 6;
- c = 16。
4.3.4 什么时候用前置,什么时候用后置?
-
如果你不需要使用自增 / 自减的返回值,只是想让变量加 1 / 减 1 :前置和后置的效果完全一样,没有任何区别。比如在 for 循环里:
// 后置写法 for (var i = 0; i < 10; i++) { console.log(i); } // 前置写法 for (var i = 0; i < 10; ++i) { console.log(i); } // 两个循环的执行结果完全一样,都是输出0-9 -
如果你需要使用自增 / 自减的返回值:就根据你的需求,选择前置或后置。需要先改值再用,就用前置;需要先用旧值再改值,就用后置。
4.4 比较运算符:判断两个值的大小 / 是否相等
比较运算符,就是用来 "比较两个值的大小、或者是否相等" 的,所有比较运算符的返回结果,都是布尔值 true 或 false,用来给条件语句做判断。
比较运算符分为两大类:大小比较运算符 和 相等性比较运算符,我们一个一个讲透。
4.4.1 大小比较运算符
| 运算符 | 含义 | 示例 | 结果 |
|---|---|---|---|
< |
小于 | 10 < 20 |
true |
> |
大于 | 10 > 20 |
false |
<= |
小于等于 | 10 <= 10 |
true |
>= |
大于等于 | 10 >= 20 |
false |
基础用法:
console.log(10 < 20); // 输出:true
console.log(10 > 20); // 输出:false
console.log(10 <= 10); // 输出:true(小于等于,只要满足其中一个就为true)
console.log(10 >= 5); // 输出:true
比较规则:
-
两边都是数字:直接比较数字的大小;
-
两边都是字符串 :按字符的 Unicode 编码,从左到右一个一个比较,不是按字符串的长度!
console.log('a' < 'b'); // 输出:true('a'的Unicode编码是97,'b'是98) console.log('A' < 'a'); // 输出:true('A'的编码是65,比'a'小) console.log('10' < '2'); // 输出:true(先比较第一个字符'1'和'2','1'的编码比'2'小,所以直接返回true,不会看后面的0) console.log('apple' < 'banana'); // 输出:true(第一个字符'a' < 'b')这是新手最容易踩的坑:两个数字字符串比较,不是按数字大小,而是按字符编码比较,比如
'10' < '2'会返回 true,一定要注意! -
一边是数字,一边是字符串 :会把字符串转成数字,再比较大小;如果字符串转不成数字,就返回 false。
console.log(10 < '20'); // 输出:true('20'转成20,10<20) console.log(100 > '20'); // 输出:true('20'转成20,100>20) console.log(10 < 'hello'); // 输出:false('hello'转成NaN,任何数和NaN比较都是false)
4.4.2 相等性比较运算符:== 和 === 的核心区别
这是 JavaScript 里最核心、最容易踩坑的知识点,我们用最大的篇幅,把它讲得明明白白,让你再也不会在这里踩坑。
| 运算符 | 含义 | 核心规则 |
|---|---|---|
== |
抽象相等(宽松相等) | 会做隐式类型转换,把两边的值转成相同类型,再比较值是否相等 |
=== |
严格相等 | 不会做隐式类型转换,只有两边的值类型相同、并且值也相同,才会返回 true,否则返回 false |
!= |
抽象不相等 | 和 == 相反,会做隐式类型转换 |
!== |
严格不相等 | 和 === 相反,不会做隐式类型转换 |
1. 严格相等 ===:最安全、最推荐的写法
=== 的规则非常简单,只有两步:
- 先看两边的值的类型是否相同,如果类型不同,直接返回 false;
- 如果类型相同,再看值是否相同,值相同就返回 true,值不同就返回 false。
举个例子:
// 类型相同,值相同:返回true
console.log(10 === 10); // 输出:true
console.log('hello' === 'hello'); // 输出:true
console.log(true === true); // 输出:true
console.log(null === null); // 输出:true
console.log(undefined === undefined); // 输出:true
// 类型不同:直接返回false,不管值是不是看起来一样
console.log(10 === '10'); // 输出:false(一个是number,一个是string)
console.log(true === 1); // 输出:false(一个是boolean,一个是number)
console.log(null === undefined); // 输出:false(一个是null类型,一个是undefined类型)
console.log(false === 0); // 输出:false(一个是boolean,一个是number)
=== 不会做任何隐式类型转换,规则简单明了,不会有任何奇怪的结果,实际开发中,所有的相等性判断,都推荐用 === 和!==,绝对不要用 == 和!=。
2. 抽象相等 ==:会做隐式类型转换,坑非常多
== 会做隐式类型转换,把两边的值转成相同的类型,再比较值是否相等。它的转换规则非常复杂,有很多反直觉的结果,新手很容易踩坑。
我们把最常见的转换规则,用大白话讲清楚:
-
如果两边类型相同:和 === 的规则完全一样,比较值是否相等;
-
如果一边是数字,一边是字符串 :把字符串转成数字,再比较;
console.log(10 == '10'); // 输出:true('10'转成10,值相等) console.log(0 == ''); // 输出:true(空字符串转成0) console.log(0 == ' '); // 输出:true(空格字符串转成0) -
如果一边是布尔值 :把布尔值转成数字(true 转 1,false 转 0),再比较;
console.log(true == 1); // 输出:true(true转1) console.log(false == 0); // 输出:true(false转0) console.log(true == '1'); // 输出:true(true转1,'1'转1) console.log(false == ''); // 输出:true(false转0,''转0) -
null 和 undefined :null == undefined 会返回 true,和其他任何值比较都返回 false;
console.log(null == undefined); // 输出:true(JS的特殊规定) console.log(null == 0); // 输出:false console.log(undefined == 0); // 输出:false -
NaN :NaN 和任何值 == 比较都返回 false,包括它自己;
console.log(NaN == NaN); // 输出:false
== 的反直觉坑示例:
console.log('' == 0); // true
console.log('' == false); // true
console.log(0 == false); // true
// 这三个互相==,但它们明明是完全不同类型的值,非常反直觉
console.log(' \t\n ' == 0); // true(全是空格、换行、制表符的字符串,转成0)
console.log([] == ![]); // true(非常经典的坑,新手根本看不懂)
这些反直觉的结果,都是因为 == 的隐式类型转换导致的,你根本记不住所有的转换规则,也没必要记。最好的方法,就是永远不要用 ==,所有的相等性判断都用 ===,就能完美避开所有的坑。
4.5 逻辑运算符:处理多个布尔条件的工具
逻辑运算符,是用来 "组合多个布尔条件,得到最终的布尔结果" 的,常用在条件语句里,组合多个判断条件。
| 运算符 | 含义 | 核心规则 | ||
|---|---|---|---|---|
&& |
逻辑与(并且) | 一假则假:只有两边都是 true,结果才是 true;只要有一个是 false,结果就是 false | ||
| ` | ` | 逻辑或(或者) | 一真则真:只要有一个是 true,结果就是 true;只有两边都是 false,结果才是 false | |
! |
逻辑非(取反) | 真变假,假变真:true 变 false,false 变 true |
4.5.1 逻辑与 &&:一假则假
&& 表示 "并且",只有当所有的条件都满足(都是 true),结果才是 true;只要有一个条件不满足(是 false),结果就是 false。
真值表:
| 左边的值 | 右边的值 | 结果 |
|---|---|---|
| true | true | true |
| true | false | false |
| false | true | false |
| false | false | false |
举个例子:
// 场景:判断一个数是不是在10到20之间(大于10 并且 小于20)
var num = 15;
if (num > 10 && num < 20) {
console.log(num + '在10到20之间');
} else {
console.log(num + '不在10到20之间');
}
// 输出:15在10到20之间
4.5.2 逻辑或 ||:一真则真
|| 表示 "或者",只要有任意一个条件满足(是 true),结果就是 true;只有当所有条件都不满足(都是 false),结果才是 false。
真值表:
| 左边的值 | 右边的值 | 结果 |
|---|---|---|
| true | true | true |
| true | false | true |
| false | true | true |
| false | false | false |
举个例子:
// 场景:判断一个数是不是小于10 或者 大于20
var num = 25;
if (num < 10 || num > 20) {
console.log(num + '小于10或者大于20');
} else {
console.log(num + '在10到20之间');
}
// 输出:25小于10或者大于20
4.5.3 逻辑非!:取反
! 表示 "取反",把 true 变成 false,把 false 变成 true。它是一个一元运算符,只需要一个操作数。
真值表:
| 原值 | 取反结果 |
|---|---|
| true | false |
| false | true |
举个例子:
var isAdult = true;
console.log(!isAdult); // 输出:false
var isStudent = false;
console.log(!isStudent); // 输出:true
// 场景:判断一个数是不是不大于10
var num = 15;
if (!(num > 10)) {
console.log(num + '不大于10');
} else {
console.log(num + '大于10');
}
// 输出:15大于10
常用技巧:!! 把任意值转成布尔值 两个感叹号 !!,可以把任意值,转换成对应的布尔值,相当于调用 Boolean() 函数。
举个例子:
console.log(!!'hello'); // 输出:true(非空字符串转true)
console.log(!!''); // 输出:false(空字符串转false)
console.log(!!10); // 输出:true(非0数字转true)
console.log(!!0); // 输出:false(0转false)
console.log(!!null); // 输出:false
console.log(!!undefined); // 输出:false
4.5.4 短路求值:逻辑运算符的核心特性
逻辑运算符有一个非常重要的特性,叫短路求值 :如果通过左边的值,已经能确定最终的结果了,就不会再执行右边的代码。
这个特性在实际开发中非常常用,我们一个一个讲透。
1. && 的短路求值:左边为 false,右边不执行
对于 a && b:
- 如果
a是 false,那么不管b是什么,最终结果一定是 false,所以 JS 引擎会直接短路,不会执行右边的 b; - 只有当
a是 true 的时候,才会执行右边的 b,看 b 的结果。
举个例子:
// 左边是false,右边的alert不会执行,不会弹框
false && alert('我不会执行');
// 左边是true,右边的alert会执行,会弹框
true && alert('我会执行');
实际开发中的常用场景:安全访问对象的属性当你要访问一个对象的属性,但不确定这个对象是不是 null/undefined 的时候,可以用 && 做安全判断,避免报错。
举个例子:
var user = null;
// 如果user是null,直接访问user.name会报错:Cannot read properties of null
// console.log(user.name); // 会报错
// 用&&安全访问:user是null,左边是false,右边不会执行,不会报错
console.log(user && user.name); // 输出:null,不会报错
// 如果user有值,就会访问name属性
var user2 = { name: '张三' };
console.log(user2 && user2.name); // 输出:张三
这个技巧,在实际开发中非常常用,用来避免 "访问空对象的属性" 导致的报错。
2. || 的短路求值:左边为 true,右边不执行
对于 a || b:
- 如果
a是 true,那么不管b是什么,最终结果一定是 true,所以 JS 引擎会直接短路,不会执行右边的 b; - 只有当
a是 false 的时候,才会执行右边的 b,看 b 的结果。
举个例子:
// 左边是true,右边的alert不会执行,不会弹框
true || alert('我不会执行');
// 左边是false,右边的alert会执行,会弹框
false || alert('我会执行');
实际开发中的常用场景:给变量设置默认值当你要给一个变量赋值,如果这个变量有值,就用它本身的值;如果它是空的(null/undefined/ 空字符串),就用默认值,这时候就可以用 ||。
举个例子:
// 场景:用户输入姓名,如果没输入,就用'匿名'作为默认值
var userName = prompt('请输入您的姓名:');
// 如果用户点击取消,userName是null;如果没输入内容,userName是空字符串,都会用'匿名'
var finalName = userName || '匿名';
alert('您好,' + finalName);
这个场景,在实际开发中非常常用,用来给变量设置兜底的默认值。
4.5.5 逻辑运算符的优先级
逻辑运算符的优先级:! > && > ||。
也就是说,JS 会先算 !,再算 &&,最后算 ||。如果你想改变运算顺序,就用括号 () 把要先算的部分括起来,括号的优先级是最高的。
举个例子:
// 优先级:先算!false=true,再算true && true=true,最后算false || true=true
console.log(false || true && !false); // 输出:true
// 用括号改变顺序:先算false||true=true,再算!false=true,最后算true && true=true
console.log((false || true) && !false); // 输出:true
建议:不要去记优先级,只要你的逻辑比较复杂,就用括号把你想先算的部分括起来,代码可读性更高,也不会出错。
4.6 位运算和移位运算:了解即可
位运算和移位运算,是直接操作数字的二进制位的,在前端基础开发中用得非常少,一般只用在底层优化、算法、加密等场景,我们这里只做简单的了解,不用深入研究。
4.6.1 位运算符
| 运算符 | 含义 | 示例 | 结果 | |||
|---|---|---|---|---|---|---|
& |
按位与:两个位都是 1,结果才是 1 | 5 & 3 |
1(5 是 101,3 是 011,按位与是 001=1) | |||
| ` | ` | 按位或:只要有一个位是 1,结果就是 1 | `5 | 3` | 7(101 | 011 = 111=7) |
~ |
按位取反:0 变 1,1 变 0 | ~5 |
-6 | |||
^ |
按位异或:两个位不同,结果才是 1 | 5 ^ 3 |
6(101 ^ 011 = 110=6) |
常用小技巧:
- 用
num & 1判断奇偶:结果是 1 就是奇数,0 就是偶数,比%2性能更好; - 用
num | 0把数字取整,相当于舍去小数部分; - 用
^交换两个变量的值,不用临时变量。
4.6.2 移位运算符
| 运算符 | 含义 | 示例 | 结果 |
|---|---|---|---|
<< |
左移:所有位向左移,右边补 0 | 5 << 1 |
10(相当于乘以 2 的 1 次方) |
>> |
有符号右移(算术右移):所有位向右移,左边补符号位 | 5 >> 1 |
2(相当于除以 2 的 1 次方,取整) |
>>> |
无符号右移(逻辑右移):所有位向右移,左边补 0 | 5 >>> 1 |
2 |
左移一位,相当于乘以 2;右移一位,相当于除以 2 取整,比乘除法性能更好,在算法里常用。
五、条件语句:让代码 "会思考、做选择"
条件语句,就是让代码根据不同的情况,执行不同的代码块,实现 "逻辑判断"。就像我们生活里:"如果下雨,就带伞;否则,就不带伞",条件语句就是让代码做这种选择的。
JavaScript 里的条件语句有三种:if 语句、三元表达式、switch 语句,我们一个一个讲透。
5.1 代码块和代码执行顺序
在讲条件语句之前,我们先搞懂两个基础概念:代码块 和 代码执行顺序。
5.1.1 代码块
代码块,就是用一对大括号 {} 包裹起来的一段代码,JS 引擎会把大括号里的代码,当成一个整体来执行。
举个例子:
{
var a = 10;
var b = 20;
console.log(a + b);
}
// 大括号里的代码会被当成一个整体,从上到下执行
条件语句、循环语句、函数里的代码,都是放在大括号里的代码块。
5.1.2 代码的默认执行顺序:从上到下,依次执行
JS 代码的默认执行顺序,是从上到下,一行一行依次执行,前面的代码执行完,才会执行后面的代码。
而条件语句,就是打破这个默认顺序,让代码 "根据条件,选择执行某一段代码块,跳过另一段代码块"。
5.2 if 语句:最常用、最灵活的条件语句
if 语句是 JS 里最常用的条件语句,它有三种形式,我们一个一个讲透,从最简单的到最复杂的。
5.2.1 形式 1:单分支 if 语句(只有 if)
这是最简单的 if 语句,只有一个条件,满足条件就执行代码块,不满足就跳过。
语法:
if (条件表达式) {
// 条件为true时,要执行的代码块
}
// 后面的代码,不管条件是否满足,都会执行
执行流程:
- 先计算
条件表达式的值,自动转换成布尔值; - 如果转换后的值是
true,就执行大括号里的代码块; - 如果转换后的值是
false,就跳过整个代码块,执行大括号后面的代码。
举个例子:
var age = 20;
// 如果年龄大于等于18,就输出"你是成年人"
if (age >= 18)
var age = 20;
// 如果年龄大于等于18,就输出"你是成年人"
if (age >= 18) {
console.log('你是成年人');
}
console.log('代码执行结束');
// 输出:
// 你是成年人
// 代码执行结束
在这个例子里,age 的值是 20,age >= 18 这个条件表达式的结果是 true,所以大括号里的代码会被执行,输出 "你是成年人";如果 age 的值是 15,age >= 18 的结果是 false,大括号里的代码就会被跳过,直接执行后面的 console.log('代码执行结束')。
这里要注意一个新手容易忽略的细节:如果 if 语句的代码块只有一行代码,大括号 {} 可以省略 ,但我强烈建议你永远不要省略大括号!
反面例子(省略大括号,非常不推荐):
var age = 20;
if (age >= 18)
console.log('你是成年人'); // 只有这一行属于if
console.log('这一行永远会执行'); // 这一行不属于if,不管条件是否满足都会执行
在这个例子里,如果你省略了大括号,只有紧跟在 if 后面的第一行代码属于 if 语句,第二行代码是独立的,不管条件是否满足都会执行。如果你缩进了第二行,看起来像是属于 if,但实际上不是,非常容易出 bug。
正面例子(永远加上大括号,推荐):
var age = 20;
if (age >= 18) {
console.log('你是成年人');
console.log('这一行只有条件满足时才会执行');
}
console.log('代码执行结束');
加上大括号,代码的逻辑非常清晰,哪些代码属于 if,哪些不属于,一眼就能看出来,永远不会出缩进导致的 bug。所以,哪怕只有一行代码,也请你务必加上大括号,这是行业里的通用规范。
5.2.2 形式 2:双分支 if...else 语句
单分支 if 语句只有 "满足条件就执行,不满足就跳过" 这一种选择;而双分支 if...else 语句,有两种选择:满足条件执行代码块 1,不满足条件执行代码块 2,二选一,一定会执行其中一个代码块。
语法:
if (条件表达式) {
// 条件为true时,执行这里的代码块1
} else {
// 条件为false时,执行这里的代码块2
}
执行流程:
- 先计算
条件表达式的值,自动转换成布尔值; - 如果转换后的值是
true,执行if后面大括号里的代码块 1,然后跳过else后面的代码块; - 如果转换后的值是
false,跳过if后面的代码块 1,执行else后面大括号里的代码块 2。
举个例子:
var age = 15;
// 如果年龄大于等于18,输出"你是成年人";否则,输出"你是未成年人"
if (age >= 18) {
console.log('你是成年人');
} else {
console.log('你是未成年人');
}
console.log('代码执行结束');
// 输出:
// 你是未成年人
// 代码执行结束
在这个例子里,age 的值是 15,age >= 18 的结果是 false,所以会跳过 if 里的代码,执行 else 里的代码,输出 "你是未成年人";如果 age 的值是 20,就会执行 if 里的代码,跳过 else 里的代码。
if...else 语句,就像我们生活里的 "二选一":"如果明天不下雨,我们就去公园;否则,我们就去图书馆",一定会选其中一个做。
5.2.3 形式 3:多分支 if...else if...else 语句
如果你的选择不止两个,而是三个、四个甚至更多,就需要用多分支 if...else if...else 语句了。它可以有多个条件,从上到下依次判断,只要有一个条件满足,就执行对应的代码块,然后跳过后面所有的 else if 和 else;如果所有条件都不满足,就执行 else 里的代码块。
语法:
if (条件表达式1) {
// 条件1为true时,执行这里
} else if (条件表达式2) {
// 条件1为false,条件2为true时,执行这里
} else if (条件表达式3) {
// 条件1、2都为false,条件3为true时,执行这里
}
// ... 可以有任意多个 else if
else {
// 所有条件都为false时,执行这里
}
执行流程:
- 先计算
条件表达式1的值; - 如果是
true,执行对应的代码块,然后跳过后面所有的else if和else,整个 if 语句结束; - 如果是
false,继续计算条件表达式2的值; - 如果
条件表达式2是true,执行对应的代码块,跳过后面的; - 以此类推,从上到下依次判断;
- 如果所有条件都是
false,执行else里的代码块。
举个例子:
// 场景:根据分数,输出对应的等级
// 90分及以上:优秀
// 80-89分:良好
// 60-79分:及格
// 60分以下:不及格
var score = 85;
if (score >= 90) {
console.log('优秀');
} else if (score >= 80) {
console.log('良好');
} else if (score >= 60) {
console.log('及格');
} else {
console.log('不及格');
}
// 输出:良好
我们来一步步拆解这个例子的执行流程:
score的值是 85;- 先判断第一个条件
score >= 90:85 >= 90 是false,跳过; - 继续判断第二个条件
score >= 80:85 >= 80 是true,执行console.log('良好'); - 执行完之后,跳过后面的
else if和else,整个 if 语句结束。
这里要注意一个非常重要的细节:else if 的顺序很重要,必须从严格到宽松,或者从高到低,不能写反了。
反面例子(顺序写反了,会出 bug):
var score = 95;
// 错误!顺序写反了
if (score >= 60) {
console.log('及格');
} else if (score >= 80) {
console.log('良好');
} else if (score >= 90) {
console.log('优秀');
} else {
console.log('不及格');
}
// 输出:及格(这是错误的!95分应该是优秀)
为什么会出 bug?因为 score 是 95,第一个条件 score >= 60 就已经满足了,直接执行了 "及格",后面的条件根本不会再判断了,所以顺序非常重要,必须从最严格的条件开始写。
正面例子(顺序正确):
var score = 95;
// 正确!从高到低,从严格到宽松
if (score >= 90) {
console.log('优秀');
} else if (score >= 80) {
console.log('良好');
} else if (score >= 60) {
console.log('及格');
} else {
console.log('不及格');
}
// 输出:优秀(正确)
另外,else 是可选的,你可以根据需求决定要不要加。如果没有 else,并且所有条件都不满足,就什么都不执行,直接跳过整个 if 语句。
5.3 falsy 值和 truthy 值:条件表达式里的隐式类型转换
在讲 if 语句的条件表达式时,我们一直说 "条件表达式的结果会自动转换成布尔值",但没有详细讲:哪些值会被转换成 true,哪些会被转换成 false?
这就是我们要讲的 falsy 值 和 truthy 值,这是 JavaScript 里非常重要的一个知识点,很多新手会在这里踩坑,我们用最详尽的文字讲透。
5.3.1 falsy 值:会被转换成 false 的值
falsy 值,就是 "在布尔上下文中(比如 if 语句的条件里),会被自动转换成 false 的值"。
JavaScript 里的 falsy 值,一共只有 6 个,我们把它们全部列出来,一个一个讲清楚:
| falsy 值 | 说明 |
|---|---|
false |
布尔值 false 本身 |
0 |
数字 0 |
-0 |
数字负 0(虽然很少见,但也是 falsy) |
'' 或 "" |
空字符串(引号里什么都没有,连空格都没有) |
null |
null 空值 |
undefined |
undefined 未定义 |
NaN |
NaN 非数字 |
只有这 6 个值是 falsy 值,会被转换成 false;除此之外的所有值,都是 truthy 值,会被转换成 true。
举个例子,验证所有 falsy 值:
// 1. false
if (false) {
console.log('false 会执行吗?'); // 不会执行
} else {
console.log('false 是 falsy 值'); // 会执行
}
// 2. 0
if (0) {
console.log('0 会执行吗?'); // 不会执行
} else {
console.log('0 是 falsy 值'); // 会执行
}
// 3. 空字符串 ''
if ('') {
console.log('空字符串会执行吗?'); // 不会执行
} else {
console.log('空字符串是 falsy 值'); // 会执行
}
// 4. null
if (null) {
console.log('null 会执行吗?'); // 不会执行
} else {
console.log('null 是 falsy 值'); // 会执行
}
// 5. undefined
var a;
if (a) {
console.log('undefined 会执行吗?'); // 不会执行
} else {
console.log('undefined 是 falsy 值'); // 会执行
}
// 6. NaN
if (NaN) {
console.log('NaN 会执行吗?'); // 不会执行
} else {
console.log('NaN 是 falsy 值'); // 会执行
}
这里要注意一个新手最容易踩的坑:全是空格的字符串 ' ' 不是 falsy 值,是 truthy 值!因为它不是空字符串,引号里有内容(空格),所以会被转换成 true。
踩坑示例:
var str = ' '; // 全是空格的字符串,不是空字符串
if (str) {
console.log('全是空格的字符串是 truthy 值,会执行这里'); // 会执行!
} else {
console.log('不会执行这里');
}
很多新手会以为 "空格不算内容",但在 JavaScript 里,空格也是一个字符,全是空格的字符串不是空字符串,是 truthy 值,一定要注意!
5.3.2 truthy 值:会被转换成 true 的值
truthy 值,就是 "在布尔上下文中,会被自动转换成 true 的值"。
判断规则非常简单:只要不是上面列出的 6 个 falsy 值,就全都是 truthy 值。
常见的 truthy 值示例:
// 1. 非0的数字(正数、负数都是)
if (10) {
console.log('10 是 truthy 值'); // 会执行
}
if (-5) {
console.log('-5 是 truthy 值'); // 会执行
}
if (3.14) {
console.log('3.14 是 truthy 值'); // 会执行
}
// 2. 非空的字符串(哪怕只有一个字符,哪怕只有一个空格)
if ('hello') {
console.log('非空字符串是 truthy 值'); // 会执行
}
if (' ') {
console.log('只有一个空格的字符串也是 truthy 值'); // 会执行
}
if ('0') {
console.log('字符串"0"也是 truthy 值'); // 会执行!注意:字符串"0"不是数字0,是truthy
}
// 3. 所有的对象(包括空对象 {})
if ({}) {
console.log('空对象 {} 是 truthy 值'); // 会执行
}
// 4. 所有的数组(包括空数组 [])
if ([]) {
console.log('空数组 [] 是 truthy 值'); // 会执行
}
// 5. 所有的函数
if (function(){}) {
console.log('函数是 truthy 值'); // 会执行
}
这里再强调一个新手容易踩的坑:字符串 "0" 是 truthy 值,不是 falsy 值 !因为它是一个非空的字符串,虽然内容是 "0",但它不是数字 0,所以会被转换成 true,一定要注意区分数字 0 和字符串 "0"。
踩坑示例:
// 数字0:falsy
if (0) {
console.log('数字0不会执行这里');
} else {
console.log('数字0是falsy'); // 会执行
}
// 字符串"0":truthy
if ('0') {
console.log('字符串"0"是truthy,会执行这里'); // 会执行!
} else {
console.log('不会执行这里');
}
5.3.3 falsy 和 truthy 的实际应用:简化条件判断
理解了 falsy 和 truthy 值,我们可以用它们来简化条件判断,让代码更简洁。
常见应用场景 1:判断变量是否有值 如果你想判断一个变量是不是 "有值"(不是 null、不是 undefined、不是空字符串),就可以直接把变量写在 if 的条件里,不用写 === null 或者 === undefined。
举个例子:
var userName = prompt('请输入您的姓名:');
// 简化写法:直接写 userName
// 如果用户点击取消,userName是null(falsy);如果没输入内容,是空字符串(falsy)
if (userName) {
alert('您好,' + userName);
} else {
alert('您好,匿名用户');
}
这个简化写法,等价于下面的复杂写法,但简洁太多了:
// 复杂写法(不推荐)
if (userName !== null && userName !== undefined && userName !== '') {
alert('您好,' + userName);
} else {
alert('您好,匿名用户');
}
常见应用场景 2:判断数组是否为空 注意:空数组 [] 是 truthy 值,所以你不能直接写 if (arr) 来判断数组是否为空,因为空数组也会返回 true。判断数组是否为空,应该用 arr.length === 0。
举个例子:
var arr = []; // 空数组
// 错误写法:空数组是truthy,会执行if里的代码
if (arr) {
console.log('数组不为空'); // 会执行,但这是错误的!
}
// 正确写法:用length判断
if (arr.length === 0) {
console.log('数组为空'); // 会执行,正确
} else {
console.log('数组不为空');
}
5.4 三元表达式:if...else 的简化写法
三元表达式,是 if...else 语句的一种简化写法,它可以用一行代码,实现 if...else 的二选一逻辑,让代码更简洁。
之所以叫 "三元",是因为它有三个操作数,是 JavaScript 里唯一一个有三个操作数的运算符。
5.4.1 三元表达式的语法
条件表达式 ? 表达式1 : 表达式2;
三个部分的含义:
条件表达式:和 if 语句里的条件一样,会被自动转换成布尔值;表达式1:如果条件为 true,就执行这个表达式,返回它的结果;表达式2:如果条件为 false,就执行这个表达式,返回它的结果。
执行流程:
- 计算
条件表达式的值,转成布尔值; - 如果是 true,计算
表达式1的值,返回这个结果; - 如果是 false,计算
表达式2的值,返回这个结果。
5.4.2 三元表达式的基础示例
我们用一个最经典的例子:"求两个数的最大值",来对比 if...else 和三元表达式的写法。
if...else 写法:
var a = 10;
var b = 20;
var max; // 用来存最大值
if (a > b) {
max = a;
} else {
max = b;
}
console.log(max); // 输出:20
三元表达式写法:
var a = 10;
var b = 20;
var max = a > b ? a : b; // 一行代码搞定
console.log(max); // 输出:20
我们来拆解这个三元表达式:
- 条件表达式:
a > b→10 > 20→false; - 因为条件是 false,所以返回
表达式2的值,也就是b的值 20; - 把 20 赋给
max。
是不是比 if...else 简洁太多了?这就是三元表达式的优势:简单的二选一逻辑,用一行代码就能搞定,代码非常精炼。
再举一个例子:
// 场景:判断年龄是否成年,成年返回'成年人',未成年返回'未成年人'
var age = 18;
var result = age >= 18 ? '成年人' : '未成年人';
console.log(result); // 输出:成年人
5.4.3 三元表达式的注意事项
虽然三元表达式很简洁,但也有一些注意事项,新手要注意:
1. 三元表达式是有返回值的,必须用变量接收,或者直接使用
三元表达式会返回一个结果(表达式 1 或表达式 2 的结果),所以你必须把这个结果赋给一个变量,或者直接用在别的地方(比如直接放在 console.log() 里),不能单独写一个三元表达式在那里。
错误写法:
var a = 10;
var b = 20;
a > b ? a : b; // 错误!单独写在这里,没有用变量接收,也没有使用,虽然不会报错,但没有任何意义
正确写法:
var a = 10;
var b = 20;
// 写法1:用变量接收
var max = a > b ? a : b;
console.log(max);
// 写法2:直接使用
console.log(a > b ? a : b); // 直接放在console.log里
2. 不要嵌套太多层三元表达式,可读性会变差
三元表达式可以嵌套,但如果嵌套超过两层,代码的可读性会变得非常差,别人根本看不懂你在写什么,这时候不如用 if...else if...else。
反面例子(嵌套太多,可读性差):
var score = 85;
// 嵌套三层,可读性非常差
var result = score >= 90 ? '优秀' : (score >= 80 ? '良好' : (score >= 60 ? '及格' : '不及格'));
console.log(result);
虽然上面的代码能运行,但可读性太差了,别人看半天才能看懂逻辑,这时候不如用 if...else if...else,逻辑更清晰。
正面例子(简单的两层嵌套可以接受,或者用 if):
var score = 85;
// 推荐用if...else if...else,逻辑更清晰
if (score >= 90) {
console.log('优秀');
} else if (score >= 80) {
console.log('良好');
} else if (score >= 60) {
console.log('及格');
} else {
console.log('不及格');
}
总结 :简单的二选一逻辑,用三元表达式,代码更简洁;复杂的多分支逻辑,用 if...else,可读性更高。
5.5 switch 语句:适合多分支 "等值判断" 的场景
switch 语句,是另一种多分支的条件语句,它特别适合 "根据一个变量的不同值,执行不同代码" 的场景,也就是 "等值判断" 的场景。
比如:"根据星期几(1-7),输出对应的中文"、"根据月份(1-12),输出对应的季节",这种场景用 switch 会比 if...else if 更清晰。
5.5.1 switch 语句的语法
switch (表达式) {
case 值1:
// 表达式的结果 === 值1 时,执行这里的代码
语句1;
break; // 跳出switch语句,非常重要!
case 值2:
// 表达式的结果 === 值2 时,执行这里的代码
语句2;
break;
case 值3:
语句3;
break;
// ... 可以有任意多个 case
default:
// 所有 case 都不匹配时,执行这里的代码
语句N;
// default 后面可以不加 break,因为它是最后一个
}
5.5.2 switch 语句的执行流程
我们一步步拆解 switch 语句的执行流程,确保你彻底搞懂:
- 先计算
switch后面表达式的值; - 从上到下,依次把这个值,和每个
case后面的值做严格相等比较(===); - 如果找到匹配的
case(也就是表达式结果 === case值),就执行这个case后面的所有语句,直到遇到break为止; - 如果执行过程中遇到
break,就立即跳出整个switch语句,后面的case和default都不会再执行; - 如果所有
case都不匹配,就执行default后面的语句; default是可选的,如果没有default,并且所有case都不匹配,就什么都不执行。
5.5.3 switch 语句的基础示例
我们用一个最经典的例子:"根据输入的数字(1-7),输出对应的星期几",来讲解 switch 的用法。
举个例子:
var day = 3; // 1代表星期一,2代表星期二,...,7代表星期日
switch (day) {
case 1:
console.log('星期一');
break;
case 2:
console.log('星期二');
break;
case 3:
console.log('星期三');
break;
case 4:
console.log('星期四');
break;
case 5:
console.log('星期五');
break;
case 6:
console.log('星期六');
break;
case 7:
console.log('星期日');
break;
default:
console.log('输入有误,请输入1-7的数字');
}
// 输出:星期三
我们来一步步拆解这个例子的执行流程:
switch后面的表达式是day,值是 3;- 从上到下依次比较:
- 和
case 1比较:3 === 1→ false,跳过; - 和
case 2比较:3 === 2→ false,跳过; - 和
case 3比较:3 === 3→ true,匹配成功;
- 和
- 执行
case 3后面的语句:console.log('星期三');; - 遇到
break,立即跳出整个switch语句,后面的case 4到default都不会再执行。
如果 day 的值是 8,所有 case 都不匹配,就会执行 default 后面的语句,输出 "输入有误,请输入 1-7 的数字"。
5.5.4 重要!break 的作用:不要忘记写 break!
在 switch 语句里,break 的作用是立即跳出整个 switch 语句 ,这是 switch 里最最重要的一个知识点,90% 的新手用 switch 都会在这里踩坑,就是因为忘记写 break!
如果你忘记写 break,代码会发生 "穿透" 现象:匹配到一个 case 之后,会继续执行后面所有 case 的语句,直到遇到 break 或者 switch 结束,不管后面的 case 是否匹配!
反面例子(忘记写 break,发生穿透):
var day = 3;
switch (day) {
case 1:
console.log('星期一');
// 忘记写 break!
case 2:
console.log('星期二');
// 忘记写 break!
case 3:
console.log('星期三');
// 忘记写 break!
case 4:
console.log('星期四');
// 忘记写 break!
case 5:
console.log('星期五');
break;
default:
console.log('输入有误');
}
// 输出:
// 星期三
// 星期四
// 星期五
我们来拆解这个错误例子的执行流程:
day是 3,匹配到case 3,执行console.log('星期三');;- 因为没有
break,代码继续向下穿透,执行case 4的console.log('星期四');; - 还是没有
break,继续向下穿透,执行case 5的console.log('星期五');; - 遇到
break,终于跳出了。
结果输出了三行内容,这显然不是我们想要的,就是因为忘记写 break 导致的。
正面例子(每个 case 后面都加上 break):
var day = 3;
switch (day) {
case 1:
console.log('星期一');
break; // 加上 break
case 2:
console.log('星期二');
break; // 加上 break
case 3:
console.log('星期三');
break; // 加上 break
case 4:
console.log('星期四');
break; // 加上 break
case 5:
console.log('星期五');
break; // 加上 break
case 6:
console.log('星期六');
break; // 加上 break
case 7:
console.log('星期日');
break; // 加上 break
default:
console.log('输入有误');
// default 是最后一个,可以不加 break
}
// 输出:星期三(正确)
每个 case 后面都加上 break,就不会发生穿透了,只会执行匹配到的那个 case 的代码,逻辑完全正确。
特殊场景:利用穿透实现多个 case 执行同一段代码 虽然忘记写 break 会出 bug,但在某些特殊场景下,我们可以主动利用穿透 ,让多个 case 执行同一段代码。
举个例子:
// 场景:根据月份,输出对应的季节
// 3、4、5月:春季
// 6、7、8月:夏季
// 9、10、11月:秋季
// 12、1、2月:冬季
var month = 4;
switch (month) {
case 3:
case 4:
case 5:
console.log('春季');
break;
case 6:
case 7:
case 8:
console.log('夏季');
break;
case 9:
case 10:
case 11:
console.log('秋季');
break;
case 12:
case 1:
case 2:
console.log('冬季');
break;
default:
console.log('输入有误,请输入1-12的月份');
}
// 输出:春季
在这个例子里,case 3、case 4、case 5 后面都没有写 break,所以不管匹配到这三个中的哪一个,都会穿透到 case 5 后面的代码,执行 console.log('春季'),然后遇到 break 跳出。这样就实现了 "多个 case 执行同一段代码" 的需求,代码非常简洁。
这是 switch 语句的一个高级技巧,利用穿透来简化代码,非常实用。
5.5.5 switch 语句的注意事项
除了 break,switch 还有一些注意事项,新手要了解:
1. switch 的比较是严格相等(===),不是抽象相等(==)
switch 里,表达式的结果和 case 后面的值,是用 严格相等 === 来比较的,不会做隐式类型转换,这一点非常重要!
举个例子:
var num = '3'; // 注意:这是字符串 '3',不是数字 3
switch (num) {
case 3: // 数字 3
console.log('匹配到数字3');
break;
case '3': // 字符串 '3'
console.log('匹配到字符串3');
break;
default:
console.log('没有匹配到');
}
// 输出:匹配到字符串3
在这个例子里,num 是字符串 '3',case 3 是数字 3,'3' === 3 是 false,所以不会匹配;只有 case '3' 是字符串,'3' === '3' 是 true,所以会匹配到。
如果是用 if 语句写 num == 3,会做隐式类型转换,返回 true,但 switch 不会,一定要注意!
2. default 的位置不一定要在最后
虽然我们习惯把 default 写在最后,但它的位置其实是任意的,可以写在任何 case 之间,不过为了代码的可读性,强烈建议你把 default 写在最后。
注意 :如果 default 没有写在最后,并且它后面还有 case,那么 default 后面也必须加上 break,否则也会发生穿透!
反面例子(default 写在中间,没加 break):
var day = 8;
switch (day) {
case 1:
console.log('星期一');
break;
default:
console.log('输入有误');
// 没加 break!
case 2:
console.log('星期二');
break;
}
// 输出:
// 输入有误
// 星期二(发生穿透了!)
因为 default 写在中间,并且没加 break,所以执行完 default 之后,会继续向下穿透,执行 case 2 的代码,这显然不是我们想要的。
总结 :为了避免麻烦,永远把 default 写在最后 ,并且 default 后面可以不加 break。
5.5.6 switch 和 if...else if 的选择
什么时候用 switch,什么时候用 if...else if?我们给你一个清晰的判断标准:
- 如果是 "等值判断" (根据一个变量的不同值,执行不同代码),并且值的数量比较多(超过 3 个),推荐用
switch,代码更清晰,比如 "根据星期几输出中文"、"根据月份输出季节"; - 如果是 "范围判断" (比如大于、小于、在某个区间),或者逻辑比较复杂,必须用
if...else if,因为switch只能做等值判断,不能做范围判断,比如 "根据分数输出等级"、"根据年龄判断是否成年"。
六、循环语句:让代码 "重复执行"
循环语句,就是让代码 "重复执行某一段代码块" 的语句,是编程里非常重要的语句,比如 "打印 1-100 的所有数字"、"计算 1-100 的和"、"遍历数组里的所有元素",都需要用循环语句。
JavaScript 里的循环语句有三种:while 循环、do...while 循环、for 循环,我们一个一个讲透,每个都讲清楚 "是什么、怎么用、有什么区别、什么时候用"。
在讲循环语句之前,我们先搞懂两个核心概念:循环变量 和 循环的三要素。
6.1 循环的核心概念:循环变量和循环三要素
所有的循环语句,都有这两个核心概念,理解了它们,你就能理解所有的循环。
6.1.1 循环变量
循环变量,就是用来 "控制循环次数" 的变量,它会在循环过程中不断变化,直到满足循环结束的条件。
比如我们要 "打印 1-10 的数字",就可以用一个变量 i,从 1 开始,每次加 1,直到 10 结束,这个 i 就是循环变量。
6.1.2 循环的三要素
所有的循环,都必须包含这三个要素,缺一不可,否则就会出问题(比如死循环):
- 循环变量的初始化 :循环开始前,给循环变量一个初始值(比如
var i = 1); - 循环条件 :判断循环是否继续执行的条件(比如
i <= 10),条件为 true 继续循环,条件为 false 结束循环; - 循环变量的更新 :每次循环结束后,更新循环变量的值(比如
i++),让循环条件最终能变成 false,结束循环。
如果缺少了 "循环变量的更新",循环条件永远是 true,循环会永远执行下去,这就是死循环,会让浏览器卡死,一定要避免!
6.2 while 循环:最基础的循环
while 循环是最基础的循环语句,它的特点是 "先判断条件,再执行循环体"。
6.2.1 while 循环的语法
// 1. 循环变量的初始化(写在while外面)
var 循环变量 = 初始值;
while (循环条件) {
// 2. 循环体:要重复执行的代码
// ...
// 3. 循环变量的更新(写在循环体里面,最后面)
循环变量的更新;
}
6.2.2 while 循环的执行流程
我们一步步拆解 while 循环的执行流程,确保你彻底搞懂:
- 先执行循环变量的初始化(这一步只在循环开始前执行一次);
- 计算循环条件的值,转成布尔值;
- 如果是
true,执行大括号里的循环体代码; - 执行完循环体,执行循环变量的更新;
- 回到步骤 2,再次计算循环条件的值;
- 如果是
false,结束整个循环,执行循环后面的代码。
6.2.3 while 循环的基础示例
我们用一个最经典的例子:"打印 1-10 的所有数字",来讲解 while 循环的用法。
举个例子:
// 1. 循环变量的初始化:i从1开始
var i = 1;
// 2. 循环条件:i <= 10 时继续循环
while (i <= 10) {
// 循环体:打印当前的i
console.log(i);
// 3. 循环变量的更新:i每次加1
i++;
}
// 输出:
// 1
// 2
// 3
// ...
// 10
我们来一步步拆解这个例子的执行流程(前几次循环):
- 初始化 :
i = 1; - 第一次判断条件 :
i <= 10→1 <= 10→true; - 执行循环体 :
console.log(1)→ 输出 1; - 更新循环变量 :
i++→i = 2; - 第二次判断条件 :
2 <= 10→true; - 执行循环体 :
console.log(2)→ 输出 2; - 更新循环变量 :
i++→i = 3; - ...... 以此类推;
- 直到
i = 11时,判断条件 :11 <= 10→false,结束循环。
完美!成功打印了 1-10 的所有数字。
6.2.4 重要!不要忘记更新循环变量!
在 while 循环里,循环变量的更新必须写在循环体里面,并且一定要写 !如果忘记更新循环变量,循环条件永远是 true,循环会永远执行下去,这就是死循环,会让浏览器卡死,电脑风扇狂转,一定要避免!
反面例子(忘记更新循环变量,死循环):
var i = 1;
while (i <= 10) {
console.log(i);
// 忘记写 i++!!
}
// 死循环!永远输出 1,浏览器会卡死!
在这个例子里,i 的值永远是 1,i <= 10 永远是 true,循环会永远执行下去,永远输出 1,浏览器会卡死,一定要避免!
正面例子(记得更新循环变量):
var i = 1;
while (i <= 10) {
console.log(i);
i++; // 记得更新循环变量!
}
// 正常执行,输出1-10
6.2.5 while 循环的进阶示例:计算 1-100 的和
我们再做一个进阶练习:"计算 1-100 所有数字的和",也就是 1 + 2 + 3 + ... + 100,结果是 5050。
思路分析:
- 需要一个变量
sum来存总和,初始值是 0(因为加法的单位元是 0,0 加任何数都等于那个数); - 循环变量
i从 1 开始,到 100 结束; - 每次循环,把
i加到sum里(sum += i); - 更新
i。
代码示例:
// 1. 初始化循环变量 i,和总和 sum
var i = 1;
var sum = 0;
while (i <= 100) {
// 把当前的 i 加到 sum 里
sum += i; // 等价于 sum = sum + i;
// 更新循环变量
i++;
}
console.log('1-100的和是:' + sum);
// 输出:1-100的和是:5050
我们来拆解前几次循环的执行流程:
- 初始化:
i = 1,sum = 0; - 第一次循环:
- 条件:
1 <= 100→ true; sum += i→sum = 0 + 1 = 1;i++→i = 2;
- 条件:
- 第二次循环:
- 条件:
2 <= 100→ true; sum += i→sum = 1 + 2 = 3;i++→i = 3;
- 条件:
- 第三次循环:
- 条件:
3 <= 100→ true; sum += i→sum = 3 + 3 = 6;i++→i = 4;
- 条件:
- ...... 以此类推;
- 最后一次循环(i=100):
- 条件:
100 <= 100→ true; sum += i→sum = 4950 + 100 = 5050;i++→i = 101;
- 条件:
- 条件:
101 <= 100→ false,结束循环; - 输出
sum:5050。
完美!成功计算出了 1-100 的和。
6.3 break 和 continue:控制循环的流程
break 和 continue 是用来 "控制循环流程" 的关键字,它们可以让循环提前结束,或者跳过某一次循环,我们一个一个讲透。
6.3.1 break:立即结束整个循环
break 关键字,我们在 switch 语句里已经见过了,它在循环里的作用是:立即结束整个循环 ,不管循环条件是不是 true,只要遇到 break,循环就立刻停止,执行循环后面的代码。
我们用一个生活里的例子理解:你要吃 5 个李子,吃到第 3 个的时候,发现里面有半个虫子,你不想吃了,直接把剩下的都扔掉,结束吃李子的过程 ------ 这就是 break。
举个例子:
// 场景:吃5个李子,吃到第3个就不吃了
var i = 1;
while (i <= 5) {
if (i === 3) {
// 吃到第3个了,遇到break,结束整个循环
break;
}
console.log('我在吃第' + i + '个李子');
i++;
}
console.log('不吃了');
// 输出:
// 我在吃第1个李子
// 我在吃第2个李子
// 不吃了
我们来拆解执行流程:
i = 1:条件 true,i !== 3,输出 "我在吃第 1 个李子",i++→ 2;i = 2:条件 true,i !== 3,输出 "我在吃第 2 个李子",i++→ 3;i = 3:条件 true,i === 3,执行break,立即结束整个循环 ,后面的console.log和i++都不会执行;- 执行循环后面的代码:输出 "不吃了"。
注意:在这个例子里,i 到 3 的时候,遇到了 break,循环直接结束了,所以第 3、4、5 个李子都没吃,并且 i++ 也没执行,i 的值还是 3。
进阶示例:找到 100-200 之间第一个 3 的倍数 需求:遍历 100-200 的数字,找到第一个是 3 的倍数的数字,打印出来,然后结束循环,不用找后面的了。
代码示例:
var i = 100;
while (i <= 200) {
if (i % 3 === 0) {
// 找到了第一个3的倍数,打印出来,然后break结束循环
console.log('找到了第一个3的倍数:' + i);
break;
}
i++;
}
// 输出:找到了第一个3的倍数:102
100 除以 3 余 1,不是;101 除以 3 余 2,不是;102 除以 3 余 0,是,所以打印出来,然后 break 结束循环,后面的 103-200 都不会再找了,效率很高。
6.3.2 continue:跳过本次循环,继续下一次
continue 关键字的作用是:跳过本次循环剩下的代码,直接进入下一次循环(循环条件还是会判断的,循环变量的更新也别忘了)。
我们用生活里的例子理解:你要吃 5 个李子,吃到第 3 个的时候,发现里面有一只虫子,你把这个扔掉,继续吃第 4 个、第 5 个 ------ 这就是 continue,只跳过这一个,后面的继续。
举个例子:
// 场景:吃5个李子,第3个有虫子,扔掉,继续吃后面的
var i = 1;
while (i <= 5) {
if (i === 3) {
// 第3个有虫子,跳过这次循环,直接进入下一次
// 注意:continue之前一定要更新循环变量!否则会死循环!
i++;
continue;
}
console.log('我在吃第' + i + '个李子');
i++;
}
// 输出:
// 我在吃第1个李子
// 我在吃第2个李子
// 我在吃第4个李子
// 我在吃第5个李子
我们来拆解执行流程:
i = 1:条件 true,i !== 3,输出 "我在吃第 1 个李子",i++→ 2;i = 2:条件 true,i !== 3,输出 "我在吃第 2 个李子",i++→ 3;i = 3:条件 true,i === 3,执行i++→ 4,然后执行continue,跳过本次循环剩下的代码 (也就是console.log),直接进入下一次循环;i = 4:条件 true,i !== 3,输出 "我在吃第 4 个李子",i++→ 5;i = 5:条件 true,i !== 3,输出 "我在吃第 5 个李子",i++→ 6;i = 6:条件 false,结束循环。
完美!第 3 个被跳过了,其他的都吃了。
重要!在 while 循环里用 continue,一定要在 continue 之前更新循环变量! 这是新手最容易踩的坑:在 while 循环里用 continue,如果 continue 之前没有更新循环变量,那么循环变量永远不会变,条件永远是 true,就会变成死循环!
反面例子(continue 之前没更新循环变量,死循环):
var i = 1;
while (i <= 5) {
if (i === 3) {
// 错误!continue 之前没更新 i!
continue;
}
console.log('我在吃第' + i + '个李子');
i++;
}
// 死循环!i 永远是 3,永远卡在 continue 这里!
在这个例子里,i = 3 的时候,执行 continue,跳过了后面的 i++,所以 i 的值永远是 3,条件永远是 true,死循环了,一定要避免!
正面例子(continue 之前先更新循环变量):
var i = 1;
while (i <= 5) {
if (i === 3) {
// 正确!continue 之前先更新 i!
i++;
continue;
}
console.log('我在吃第' + i + '个李子');
i++;
}
// 正常执行
或者,你也可以把循环变量的更新放在 if 之前,这样就不用担心了,但这种写法比较少见,还是推荐上面的写法。
进阶示例:找到 100-200 之间所有 3 的倍数 需求:遍历 100-200 的数字,找到所有是 3 的倍数的数字,打印出来,不是 3 的倍数的就跳过。
代码示例:
var i = 100;
while (i <= 200) {
if (i % 3 !== 0) {
// 不是3的倍数,跳过这次循环,继续下一次
i++;
continue;
}
// 是3的倍数,打印出来
console.log('找到了3的倍数:' + i);
i++;
}
// 输出:
// 找到了3的倍数:102
// 找到了3的倍数:105
// ...
// 找到了3的倍数:198
这个例子里,不是 3 的倍数的数字,就执行 continue 跳过,不打印;是 3 的倍数的,就打印出来,完美实现了需求。
6.3.3 break 和 continue 的区别
我们用一个表格对比 break 和 continue,让你一眼就能看清区别:
| 关键字 | 作用 | 对循环的影响 |
|---|---|---|
break |
立即结束整个循环 | 循环彻底停止,执行循环后面的代码 |
continue |
跳过本次循环剩下的代码 | 循环不停止,直接进入下一次循环 |
6.4 do...while 循环:至少执行一次的循环
do...while 循环是 while 循环的一个变体,它的特点是:先执行一次循环体,再判断条件 。也就是说,循环体至少会执行一次,哪怕条件一开始就是 false。
6.4.1 do...while 循环的语法
// 1. 循环变量的初始化
var 循环变量 = 初始值;
do {
// 2. 循环体:至少会执行一次
// ...
// 3. 循环变量的更新
循环变量的更新;
} while (循环条件); // 注意:这里的分号不能省略!
6.4.2 do...while 循环的执行流程
- 先执行循环变量的初始化(一次);
- 直接执行循环体代码(第一次循环,不判断条件!);
- 执行循环变量的更新;
- 计算循环条件的值;
- 如果是
true,再次执行循环体; - 如果是
false,结束循环。
核心区别 :while 是 "先判断,后执行";do...while 是 "先执行,后判断",所以 do...while 的循环体至少会执行一次。
6.4.3 do...while 循环的基础示例
我们用一个例子对比 while 和 do...while,让你看清它们的区别。
示例 1:while 循环,条件一开始就是 false
var i = 11;
while (i <= 10) {
console.log('while 循环的循环体执行了');
i++;
}
console.log('while 循环结束');
// 输出:
// while 循环结束
// (循环体一次都没执行!)
因为 i 一开始就是 11,i <= 10 是 false,所以 while 循环的循环体一次都没执行,直接结束了。
示例 2:do...while 循环,条件一开始就是 false
var i = 11;
do {
console.log('do...while 循环的循环体执行了');
i++;
} while (i <= 10);
console.log('do...while 循环结束');
// 输出:
// do...while 循环的循环体执行了
// do...while 循环结束
// (循环体执行了一次!)
虽然 i 一开始就是 11,i <= 10 是 false,但 do...while 是 "先执行,后判断",所以循环体还是执行了一次,然后才判断条件,结束循环。
这就是 do...while 最大的特点:循环体至少执行一次。
6.4.4 do...while 循环的实际应用场景
do...while 循环适合 "需要先执行一次,再根据结果判断是否继续" 的场景,比如 "让用户输入密码,输入正确就结束,输入错误就继续让用户输入"。
举个例子:
// 场景:用户输入密码,正确密码是'123456',输入正确就结束,错误就继续
var password;
do {
// 先让用户输入一次
password = prompt('请输入密码:');
} while (password !== '123456'); // 如果输入的不是正确密码,继续循环
alert('密码正确,登录成功!');
这个例子里,我们需要先让用户输入一次密码,然后再判断是否正确,如果不正确就继续输入,这时候用 do...while 就非常合适,因为至少要让用户输入一次。
如果用 while 循环写,就需要先给 password 一个初始值,代码会稍微啰嗦一点,不如 do...while 直观。
6.4.5 do...while 循环的注意事项
while后面的分号不能省略! 在do...while循环里,while (循环条件)后面必须加一个分号;,这是语法规定,虽然有些浏览器不加也能运行,但为了规范,必须加上!- 同样要注意循环变量的更新,避免死循环;
- 同样可以用 break 和 continue,规则和 while 一样。
6.5 for 循环:最常用、最灵活的循环
for 循环是 JavaScript 里最常用、最灵活的循环语句,它把 "循环变量的初始化"、"循环条件"、"循环变量的更新" 这三要素,都写在 for 后面的括号里,结构非常清晰,特别适合 "知道循环次数" 的场景(比如 "循环 10 次"、"遍历 1-100")。
6.5.1 for 循环的语法
for (表达式1:循环变量初始化; 表达式2:循环条件; 表达式3:循环变量更新) {
// 循环体:要重复执行的代码
}
三个表达式的作用:
- 表达式 1 :循环变量的初始化,只在循环开始前执行一次;
- 表达式 2:循环条件,每次循环前都执行,判断是否继续循环;
- 表达式 3:循环变量的更新,每次循环体执行完后执行。
6.5.2 for 循环的执行流程
我们一步步拆解 for 循环的执行流程,这是最重要的部分,一定要彻底搞懂:
- 执行表达式 1(循环变量初始化):只在循环开始前执行一次;
- 执行表达式 2(循环条件):计算值,转成布尔值;
- 如果是
true,执行大括号里的循环体; - 执行完循环体,执行表达式 3(循环变量更新);
- 回到步骤 2,再次执行表达式 2;
- 如果是
false,结束循环。
6.5.3 for 循环的基础示例
我们还是用最经典的例子:"打印 1-10 的所有数字",来对比 for 循环和 while 循环的写法。
while 循环写法:
// 1. 初始化(写在外面)
var i = 1;
// 2. 循环条件(写在while后面)
while (i <= 10) {
console.log(i);
// 3. 更新(写在循环体里)
i++;
}
for 循环写法:
// 1. 初始化; 2. 条件; 3. 更新(都写在for后面的括号里)
for (var i = 1; i <= 10; i++) {
console.log(i);
}
// 输出:1 2 3 ... 10
是不是比 while 循环简洁太多了?三个要素都写在一行里,结构非常清晰,一眼就能看清循环的初始值、条件、更新方式,这就是 for 循环的优势。
我们来一步步拆解这个 for 循环的执行流程:
- 执行表达式 1 :
var i = 1(只执行一次); - 执行表达式 2 :
i <= 10→1 <= 10→true; - 执行循环体 :
console.log(1); - 执行表达式 3 :
i++→i = 2; - 回到步骤 2 :执行表达式 2 →
2 <= 10→true; - 执行循环体 :
console.log(2); - 执行表达式 3 :
i++→i = 3; - ...... 以此类推;
- 直到
i = 11,执行表达式 2 :11 <= 10→false,结束循环。
完美!和 while 循环的结果一样,但代码更简洁,结构更清晰。
6.5.4 for 循环的进阶示例:计算 1-100 的和
我们再用 for 循环实现 "计算 1-100 的和",对比一下 while 的写法。
for 循环写法:
var sum = 0;
// 循环变量 i 从1开始,到100结束,每次加1
for (var i = 1; i <= 100; i++) {
sum += i;
}
console.log('1-100的和是:' + sum);
// 输出:5050
是不是比 while 循环简洁多了?所有循环相关的代码都写在 for 后面的括号里,循环体里只需要写核心逻辑 sum += i,代码非常精炼。
6.5.5 for 循环的注意事项
-
三个表达式都是可选的,但分号不能省略
for循环的三个表达式都是可选的,你可以根据需求省略其中的一个或多个,但两个分号;绝对不能省略!举个例子,省略表达式 1(初始化):
var i = 1; // 初始化写在外面 for (; i <= 10; i++) { // 表达式1省略了,但分号不能省 console.log(i); }举个例子,省略表达式 3(更新):
for (var i = 1; i <= 10;) { // 表达式3省略了,分号不能省 console.log(i); i++; // 更新写在循环体里 }举个例子,三个表达式都省略(死循环):
for (;;) { // 两个分号不能省 console.log('死循环'); }这种写法是死循环,和
while (true)一样,除非有break,否则永远执行。虽然可以省略,但强烈建议你不要省略,把三个表达式都写全,代码结构最清晰,最不容易出 bug。
-
循环变量可以用 let 声明(推荐) 我们后面讲作用域的时候会说,用
let声明循环变量,比用var更好,避免变量污染,推荐用let。推荐写法:
// 用 let 声明循环变量 i for (let i = 1; i <= 10; i++) { console.log(i); } -
for 循环里同样可以用 break 和 continue 规则和
while循环完全一样,这里就不再重复了。
6.5.6 for 循环和 while 循环的选择
什么时候用 for,什么时候用 while?判断标准很清晰:
- 如果知道循环的次数 (比如 "循环 10 次"、"遍历 1-100"),优先用 for 循环,结构更清晰;
- 如果不知道循环的次数 (比如 "一直输入,直到输入正确为止"),用 while 循环 或者
do...while循环。
6.6 循环的嵌套:循环里面还有循环
循环的嵌套,就是在一个循环的循环体里,再写另一个循环,外面的叫 "外层循环",里面的叫 "内层循环"。
循环嵌套在实际开发中也很常用,比如 "打印九九乘法表"、"打印二维图形(矩形、三角形)",都需要用循环嵌套。
6.6.1 循环嵌套的基础示例:打印矩形
我们用一个简单的例子:"打印一个 5 行 5 列的星号矩形",来讲解循环嵌套的用法。
需求:打印一个这样的矩形:
*****
*****
*****
*****
*****
思路分析:
- 外层循环:控制 "行数",一共 5 行,所以外层循环
i从 1 到 5; - 内层循环:控制 "每一行的列数",每一行有 5 个星号,所以内层循环
j从 1 到 5; - 内层循环的循环体:打印一个星号(注意:不要换行);
- 内层循环结束后(也就是每一行打印完 5 个星号后),打印一个换行符
\n,让下一行从新的一行开始。
代码示例:
// 外层循环:控制行数,5行
for (var i = 1; i <= 5; i++) {
// 内层循环:控制每一行的列数,5列
for (var j = 1; j <= 5; j++) {
// 打印一个星号,不换行
process.stdout.write('*'); // 注意:这里用 process.stdout.write 代替 console.log,因为 console.log 会自动换行
}
// 内层循环结束,打印一个换行符
console.log();
}
注意 :在 Node.js 环境里,console.log() 会自动在结尾加一个换行符,所以我们用 process.stdout.write() 来打印星号,它不会自动换行;如果是在浏览器环境里,可以用 document.write() 或者拼接字符串的方式。
我们来拆解执行流程:
- 外层循环 i=1(第一行) :
- 内层循环 j=1 到 5:打印 5 个星号
*****; - 内层循环结束,打印换行符;
- 内层循环 j=1 到 5:打印 5 个星号
- 外层循环 i=2(第二行) :
- 内层循环 j=1 到 5:打印 5 个星号
*****; - 内层循环结束,打印换行符;
- 内层循环 j=1 到 5:打印 5 个星号
- ...... 以此类推;
- 外层循环 i=5(第五行)结束,整个循环结束。
完美!成功打印了一个 5 行 5 列的矩形。
6.6.2 循环嵌套的进阶示例:打印九九乘法表
我们再做一个经典的进阶练习:"打印九九乘法表"。
需求:打印这样的九九乘法表:
1*1=1
1*2=2 2*2=4
1*3=3 2*3=6 3*3=9
...
1*9=9 ... 9*9=81
思路分析:
- 外层循环:控制 "行数",也就是 "第二个乘数",从 1 到 9;
- 内层循环:控制 "每一行的列数",也就是 "第一个乘数",从 1 到 当前的行数(i);
- 内层循环的循环体:打印
j*i=结果,后面加一个制表符\t对齐; - 内层循环结束后,打印一个换行符。
代码示例:
// 外层循环:i 是第二个乘数,从1到9
for (var i = 1; i <= 9; i++) {
// 内层循环:j 是第一个乘数,从1到i
for (var j = 1; j <= i; j++) {
// 打印 j*i=结果,加制表符对齐
process.stdout.write(j + '*' + i + '=' + (j * i) + '\t');
}
// 每一行结束,打印换行
console.log();
}
完美!成功打印了九九乘法表。循环嵌套的核心就是:外层循环控制 "行",内层循环控制 "列",理解了这一点,所有的循环嵌套问题都能迎刃而解。
结语
恭喜你!终于看完了这篇超详细的 JavaScript 基础语法博客!我们来总结一下这篇博客讲了哪些核心内容:
变量
- 变量的本质:带名字的收纳盒;
- 变量的创建:声明 + 初始化;
- 变量的语法规则:英文标点、分号、命名规则、小驼峰;
var和let的使用;- 动态类型:运行时确定类型,类型可以改变;
typeof运算符:检测类型。
基本数据类型
number:所有数字,特殊值 Infinity、-Infinity、NaN,0.1+0.2 的坑;string:单双引号、转义字符、length、拼接;boolean:true 和 false;undefined:只声明不赋值;null:主动赋值的空值;- falsy 值和 truthy 值。
运算符
- 算术运算符:+、-、*、/、%;
- 赋值运算符:=、+=、-= 等;
- 自增自减:++、--,前置和后置的区别;
- 比较运算符:<、>、<=、>=,== 和 === 的核心区别(推荐用 ===);
- 逻辑运算符:&&、||、!,短路求值;
- 位运算和移位运算:了解即可。
条件语句
if语句:单分支、双分支、多分支;- falsy 和 truthy 在条件里的应用;
- 三元表达式:if...else 的简化写法;
switch语句:等值判断,break 的作用,穿透的利用。
循环语句
- 循环的三要素:初始化、条件、更新;
while循环:先判断,后执行;do...while循环:先执行,后判断,至少执行一次;for循环:最常用,结构清晰;break:结束整个循环;continue:跳过本次循环;- 循环的嵌套:外层控制行,内层控制列。
数组
- 数组的概念:带编号的收纳柜;
- 创建数组:推荐用字面量
[]; - 数组的特点:类型可以不同,长度可以动态改变;
- 数组的下标:从 0 开始!
- 获取和修改元素:
数组名[下标]; length属性:获取长度,获取最后一个元素,截断数组;- 遍历数组:用 for 循环,
i从 0 开始; - 不要给数组名直接赋值!
这篇博客的内容非常多,每一个知识点都讲得非常详细,建议你多看几遍,并且把每一个例子都自己动手敲一遍,运行一下,修改一下,只有自己动手,才能真正理解!
学习编程没有捷径,多写代码、多调试、多思考,是最好的方法!祝你在 JavaScript 的学习道路上越走越远!