3.5 操作符
ECMA-262 描述了一组可用于操作数据值的操作符,包括数学操作符(如加、减)、位操作符、关系操作符和相等操作符等。
ECMAScript 中的操作符是独特的,因为它们可用于各种值,包括字符串、数值、布尔值,甚至还有对象。在应用给对象时,操作符通常会调用 valueOf()
和 /
或 toString()
方法来取得可以计算的值.
3.5.1 一元操作符(略) ++
--
+
-
-
递增
++
/ 递减--
操作符
分为前缀版和后缀版,
前缀版位于要操作的变量前头,变量的值都会在语句被求值之前改变。
++age, --age
后缀版位于要操作的变量后头,变量的值都会在语句被求值之后改变。
age++, age–
-
一元加和减
+
-
一元加由一个加号(+)表示,放在变量前头,对数值没有任何影响。应用到非数值时,则会执行与使用Number()
转型函数一样的类型转换:数值直接返回,null
返回0,布尔值false
和true
转换为 0 和 1,字符串根据特殊规则进行解析,对象会调用它们的valueOf()
和/
或toString()
方法以得到可以转换的值。一元减由一个减号(-)表示,放在变量前头,对数值使用一元减会将其变成相应的负值。在应用到非数值时,一元减会遵循与一元加同样的规则,先对它们进行转换,然后再取负值。
一元加和减操作符主要用于基本的算术,但也可以用于数据类型转换。
let s1 = "01"; let s2 = "1.1"; let s3 = "z"; let a = null; let b = false; let f = 1.1; let o = { valueOf() { return -1; } }; s1 = +s1; // 值变成数值 1 s2 = +s2; // 值变成数值 1.1 s3 = +s3; // 值变成 NaN a = +a; // 值变成0 b = +b; // 值变成数值 0 f = +f; // 不变,还是 1.1 o = +o; // 值变成数值-1
let s1 = "01"; let s2 = "1.1"; let s3 = "z"; let b = false; let a = null; let f = 1.1; let o = { valueOf() { return -1; } }; s1 = -s1; // 值变成数值-1 s2 = -s2; // 值变成数值-1.1 s3 = -s3; // 值变成 NaN a = +a; // 值变成0 b = -b; // 值变成数值 0 f = -f; // 变成-1.1 o = -o; // 值变成数值 1
3.5.2 位操作符(略)
位操作符用于数值的底层操作,也就是操作内存中表示数据的比特(位)。ECMAScript 中的所有数值都以 IEEE 754 64 位格式存储,但位操作并不直接应用到 64 位表示,而是先把值转换为32 位整数,再进行位操作,之后再把结果转换为 64 位。
对开发者而言,就好像只有 32 位整数一样,因为 64 位整数存储格式是不可见的。因此只需要考虑 32 位整数。
有符号整数使用 32 位的前 31 位表示整数值。第 32 位表示数值的符号,如 0 表示正,1 表示负。这一位称为符号位(sign bit),它的值决定了数值其余部分的格式。
正值以真正的二进制格式存储,即 31 位中的每一位都代表 2 的幂。
18 : 10010
负值以补码的二进制编码存储。
补码(也叫二补数) = 正值的反码(也叫一补数) + 1
ECMAScript 在把负值输出为一个二进制字符串时,会得到一个前面加了减号的绝对值,如下所示:
let num = -18;
console.log(num.toString(2)); // "-10010"
在将 -18
转换为二进制字符串时,结果得到 -10010
。转换过程会求得二补数(补码),然后再以更符合逻辑的形式表示出来
在对 ECMAScript 中的数值应用位操作符时,后台会发生转换:64 位数值会转换为 32 位数值,然后执行位操作,最后再把结果从 32 位转换为 64 位存储起来。整个过程就像处理 32 位数值一样,这让二进制操作变得与其他语言中类似。但这个转换也导致了一个奇特的副作用,即特殊值NaN
和Infinity
在位操作中都会被当成 0 处理。
如果将位操作符应用到非数值,那么首先会使用 Number()函数将该值转换为数值(这个过程是自动的),然后再应用位操作,最终结果是数值。
-
按位非
~
按位非的最终效果是对数值取反并减 1
~ num1
即为- num1 - 1
let num1 = 25; // 二进制 00000000000000000000000000011001 let num2 = ~num1; // 二进制 11111111111111111111111111100110 console.log(num2); // -26 let num3 = -num1 -1; console.log(num3); // -26 console.log(~ num1 == - num1 - 1); // true
-
按位与
&
按位与操作符用和号(&
)表示,有两个操作数。本质上,按位与就是将两个数的每一个位对齐,然后基于真值表中的规则(两个位都是1时返回1,任何一位是0时返回0),对每一位执行相应的与操作。 -
按位或
|
按位或操作符用管道符(|
)表示,有两个操作数。按位或操作在至少一位是 1 时返回 1,两位都是 0 时返回 0。 -
按位异或
^
按位异或用脱字符(^
)表示,同样有两个操作数。按位异或操作在 两位相同(同为0或者同为1)时为0, 不同时为1。
-
左移
<<
左移操作符用两个小于号(
<<
)表示,会按照指定的位数将数值的所有位向左移动,以0补充数值右端的空位。但会保留它所操作数值的符号位。数值 2(二进制 10)向左移 5 位,就会得到 64(二进制 1000000)
-2 左移 5 位,将得到-64。
-
有符号右移
>>
有符号右移由两个大于号(>>)表示,会将数值的所有 32 位都向右移,同时保留符号(正或负)。
有符号右移实际上是左移的逆运算。比如,如果将 64 右移 5 位是2, -64右移5位是-2。64 : 00000000 00000000 00000000 01000000
-64 负值以补码的形式存储, 补码 = 反码 + 1
反码: 11111111 11111111 11111111 10111111
+1: 11111111 11111111 11111111 11000000
所以, -64 的 二进制存储表示为 11111111 11111111 11111111 11000000把 -64 转换为二进制字符串输出表示时会求其补码, 以前面带减号的绝对值表示。
-64 的 二进制表示 11111111 11111111 11111111 11000000
-64 求补码:
反码: 00000000 00000000 00000000 00111111
加1: 00000000 00000000 00000000 01000000所以,-64 输出为 二进制字符串 为 - 1000000
同样,移位后就会出现空位。不过,右移后空位会出现在左侧,且在符号位之后。ECMAScript 会用符号位的值来填充这些空位,以得到完整的数值。
-
无符号右移
>>>
无符号右移用 3 个大于号表示(>>>),会将数值的所有 32 位都向右移, 统一给空位补 0,不管符号位是什么。对于正数,无符号右移与有符号右移结果相同。
对于负数,无符号右移操作符将负数的二进制表示当成正数的二进制表示来处理。因为负数是其绝对值的补码,所以右移之后结果变得非常之大。64 : 00000000 00000000 00000000 01000000
-64 负值以补码的形式存储, 补码 = 反码 + 1
反码: 11111111 11111111 11111111 10111111
+1: 11111111 11111111 11111111 11000000
所以, -64 的 二进制存储表示为 11111111 11111111 11111111 11000000let oldValue = -64; // 等于二进制 11111111 11111111 11111111 11000000 let newValue = oldValue >>> 5; // 等于十进制 134217726 console.log(oldValue, newValue); // -64 134217726 console.log(oldValue.toString(2)); // -1000000 console.log(newValue.toString(2)); // 右移五位前面补0,为 00000111 11111111 11111111 11111110
在对-64 无符号右移 5 位后,结果是 134 217 726。这是因为-64 的二进制表示是 11111111 11111111 11111111 11000000,无符号右移却将它当成正值,也就是 4 294 967 232。把这个值右移 5 位后,结果是
00000111 11111111 11111111 11111110,即 134 217 726。
3.5.3 布尔操作符 !
&&
||
布尔操作符一共有 3 个:逻辑非!
、逻辑与&&
、逻辑或||
。
-
逻辑非
!
逻辑非
!
操作符首先将操作数转换为布尔值,然后再对其取反,始终返回布尔值。 如果操作数是对象,则返回 false。
如果操作数是空字符串,则返回 true。
如果操作数是非空字符串,则返回false。
如果操作数是数值 0,则返回 true。
如果操作数是非 0 数值(包括 Infinity),则返回 false。
如果操作数是 null,则返回 true。
如果操作数是 NaN,则返回 true。
如果操作数是undefined,则返回 true。let a = { B: 12 }; let c; console.log(!a); // false console.log(!false); // true console.log(!""); // true console.log(!"blue"); // false console.log(!0); // true console.log(!12345); // false console.log(!NaN); // true console.log(!null); // true console.log(!c); // true
即
各种非空值 转换为布尔值为 true, 逻辑非取反为false
各种空值 转换为布尔值为false,逻辑非取反为true逻辑非操作符也可以用于把任意值转换为布尔值。同时使用两个叹号(
!!
),相当于调用了转型函数Boolean()
。无论操作数是什么类型,第一个叹号总会返回布尔值。第二个叹号对该布尔值取反,从而给出变量真正对应的布尔值。结果与对同一个值使用Boolean()
函数是一样的。console.log(!!"blue"); // true console.log(!!0); // false console.log(!!NaN); // false console.log(!!""); // false console.log(!!12345); // true
-
逻辑与
&&
逻辑与操作符由两个和号(&&)表示,应用到两个操作数。
let result = A && B
,仅当 A 和 B 均为ture
时, result 为true
。逻辑与操作符是一种短路操作符:如果第一个操作数决定了结果,那么永远不会对第二个操作数求值。
对逻辑与操作符来说,如果第一个操作数是 false,那么无论第二个操作数是什么值,结果也不可能等于 true,因此第一个操作数是false时就可就决定结果,不会对第二个操作数求值。
上例中 someUndeclaredVariable 没有事先声明,变量 found 的值是 true,逻辑与操作符会继续求值变量 someUndeclaredVariable。
但是由于 someUndeclaredVariable 没有定义,不能对它应用逻辑与操作符,因此就报错了。而变量 found 的值是 false 时,即使变量 someUndeclaredVariable 没有定义,由于第一个操作数是 false,逻辑与操作符也不会对它求值,因此会输出结果 false。逻辑与操作符可用于任何类型的操作数,不限于布尔值。如果有操作数不是布尔值,则逻辑与并不一定会返回布尔值,而是遵循如下规则。
如果第一个操作数是对象,则返回第二个操作数。
如果第二个操作数是对象,则只有第一个操作数求值为true
才会返回该对象。
如果两个操作数都是对象,则返回第二个操作数。
如果有一个操作数是null
,则返回null
。
如果有一个操作数是NaN
,则返回NaN
。
如果有一个操作数是undefined
,则返回undefined
。
-
逻辑或
||
let result = A || B
,只要A 和 B 有一个是ture
, result 就为true
。与逻辑与类似,如果有一个操作数不是布尔值,那么逻辑或操作符也不一定返回布尔值。它遵循如下规则。
如果第一个操作数是对象,则返回第一个操作数。
如果第一个操作数求值为false
,则返回第二个操作数。
如果两个操作数都是对象,则返回第一个操作数。
如果两个操作数都是null
,则返回null
。
如果两个操作数都是NaN
,则返回NaN
。
如果两个操作数都是undefined
,则返回undefined
。逻辑或操作也具有短路特性,第一个操作数求值为
true
时,第二个操作数就不会被求值了。
利用这个行为,可以避免给变量赋值 null 或 undefined。比如:let myObject = preferredObject || backupObject;
在这个例子中,变量 myObject 会被赋予两个值中的一个。其中,preferredObject 变量包含首选的值,backupObject 变量包含备用的值。如果 preferredObject 不是 null,则它的值就会赋给myObject;如果 preferredObject 是 null,则 backupObject 的值就会赋给 myObject。这种模式在 ECMAScript 代码中经常用于变量赋值。
3.5.4 乘性操作符 *
/
%
ECMAScript 定义了 3 个乘性操作符:乘法*
、除法/
和取模%
。
在处理非数值时,它们会包含一些自动的类型转换。如果乘性操作符有不是数值的操作数,则该操作数会在后台被使用 Number()
转型函数转换为数值。这意味着空字符串会被当成 0,而布尔值 true 会被当成 1。
3.5.5 指数操作符 **
ES7新增了指数操作符,Math.pow()
现在有了自己的操作符**
,结果是一样的.
console.log(Math.pow(3, 2)); // 9
console.log(3 ** 2); // 9
console.log(Math.pow(16, 0.5)); // 4
console.log(16** 0.5); // 4
指数操作符也有自己的指数赋值操作符**=
,该操作符执行指数运算和结果的赋值操作:
squared **= 2;
即等价于 squared = squared ** 2;
let squared = 3;
squared **= 2;
console.log(squared); // 9
let sqrt = 16;
sqrt **= 0.5;
console.log(sqrt); // 4
3.5.6 加性操作符 +
-
加性操作符,即加法和减法操作符。
-
加法操作符
ECMAScript 中最常犯的一个错误,就是忽略加法操作中涉及的数据类型。let num1 = 5; let num2 = 10; let message = "The sum of 5 and 10 is " + num1 + num2; console.log(message); // "The sum of 5 and 10 is 510" let message1 = "The sum of 5 and 10 is " + (num1 + num2); console.log(message1); // "The sum of 5 and 10 is 15"
变量 message 中保存的是一个字符串,是执行两次加法操作之后的结果。第一次加法的操作数是一个字符串和一个数值(5),结果还是一个字符串。第二次加法仍然是用一个字符串去加一个数值(10),同样也会得到一个字符串。
如果想真正执行数学计算,然后把结果追加到字符串末尾,只要使用一对括号即可。
变量message1 中用括号把两个数值变量括了起来,让解释器先执行两个数值的加法,然后再把结果追加给字符串。因此,最终得到的字符串变成了"The sum of 5 and 10 is 15"。
-
减法操作符
3.5.7 关系操作符 <
<=
>
>=
关系操作符执行比较两个值的操作,包括小于(<
)、大于(>
)、小于等于(<=
)和大于等于(>=
),都返回布尔值
1、在使用关系操作符比较两个字符串时,需要注意,关系操作符比较的是字符串中对应字符的编码,这些编码是数值,比较以后返回布尔值。
"a"的编码97,"A"编码65, 即大写字母的编码永远小于小写字母的编码。
let result = "Brick" < "alphabet"; // true
要得到确实按字母顺序比较的结果,就必须把两者都转换为相同的大小写形式(全大写或全小写),然后再比较。
let result = "Brick".toLowerCase() < "alphabet".toLowerCase(); // false
注意,关系操作符在比较两个数值字符串时,关系操作符比较的是字符串中对应字符的编码,因此会逐个比较他们的字符编码,字符"0"的编码是48,"1"是49,"2"是50,"3"是51……
let result = "23" < "3"; // true 字符2与字符3比较
2、但若是数值和字符串比较,则字符串就会先被转换为数值,然后进行数值比较。
let result = "23" < 3; // false 数值23与3比较
若字符串不能转换为数值,只能转换为NaN
,则如下:
let result = "a" < 3; // 因为"a"会转换为 NaN,所以结果是 false
这里有一个规则,即任何关系操作符在涉及比较 NaN 时都返回 false。
所以有下面的特殊示例:
let result1 = NaN < 3; // false
let result2 = NaN >= 3; // false
在大多数比较的场景中,如果一个值不小于另一个值,那就一定大于或等于它。但在比较 NaN 时,无论是小于还是大于等于,比较的结果都会返回 false。
3.5.8 相等操作符 ==
!=
===
!==
ECMAScript 提供了两组操作符。第一组是等于和不等于,它们在比较之前执行转换。第二组是全等和不全等,它们在比较之前不执行转换。
-
等于
==
和不等于!=
(强制类型转换)ECMAScript 中的等于操作符用两个等于号(
==
)表示,如果操作数相等,则会返回true
。不等于操作符用叹号和等于号(!=
)表示,如果两个操作数不相等,则会返回true
。==
和!=
这两个操作符都会先进行类型转换(通常称为强制类型转换)再确定操作数是否相等。
-
全等于
===
和 不全等!==
全等与不全等操作符在比较时,不转换操作数。全等操作符由 3 个等于号(
===
)表示,只有两个操作数在不转换的前提下相等才返回 truelet result1 = ("55" == 55); // true,转换后相等 let result2 = ("55" === 55); // false,不相等,因为数据类型不同
不全等操作符用一个叹号和两个等于号(
!==
)表示,只有两个操作数在不转换的前提下不相等才返回 truelet result1 = ("55" != 55); // false,转换为数值后相等 let result2 = ("55" !== 55); // true,不相等,因为数据类型不同
注意,对于
null
和undefined
,相等操作符遵循null == undefined
,而全等操作符认为null
和undefined
数据类型不同null == undefined // true null === undefined // false
注意: 由于相等和不相等操作符存在类型转换问题,因此推荐使用全等和不全等操作符。这样有助于在代码中保持数据类型的完整性。
3.5.9 条件操作符 xxxx ? A : B
条件操作符是 ECMAScript 中用途最为广泛的操作符之一,语法跟 Java 中一样。
variable = boolean_expression ? true_value : false_value;
上面的代码执行了条件赋值操作,即根据条件表达式 boolean_expression 的值决定将哪个值赋给变量 variable 。如果 boolean_expression 是 true ,则赋值 true_value ;如果boolean_expression 是 false,则赋值 false_value。
3.5.10 赋值操作符 =
简单赋值用等于号(=)表示,将右手边的值赋给左手边的变量。
复合赋值使用乘性、加性或位操作符后跟等于号(=)表示。
let num = 10;
num = num + 10; // 简单赋值
num += 10; // 复合赋值
每个数学操作符以及其他一些操作符都有对应的复合赋值操作符:
3.5.11 逗号操作符
逗号操作符可以用来在一条语句中执行多个操作,如下所示,在一条语句中同时声明多个变量是逗号操作符最常用的场景。
let num1 = 1, num2 = 2, num3 = 3;
也可以使用逗号操作符来辅助赋值。
在赋值时使用逗号操作符分隔值,最终会返回表达式中最后一个值:
let num = (5, 1, 4, 8, 0); // num 的值为 0
在这个例子中,num 将被赋值为 0,因为 0 是表达式中最后一项。逗号操作符的这种使用场景并不多见,但这种行为的确存在。
3.6 语句
ECMA-262 描述了一些语句(也称为流控制语句),而 ECMAScript 中的大部分语法都体现在语句中。语句通常使用一或多个关键字完成既定的任务。
3.6.1 if
语句
if
语法:if (condition) statement1 else statement2
可以连续使用多个 if 语句:
if (condition1) statement1 else if (condition2) statement2 else statement3
条件(condition)可以是任何表达式,并且求值结果不一定是布尔值。ECMAScript 会自动调用 Boolean()
函数将这个表达式的值转换为布尔值。
如果条件求值为 true
,则执行语句statement1;如果条件求值为 false
,则执行语句 statement2。这里的语句可能是一行代码,也可能是一个代码块(即包含在一对花括号中的多行代码)。
3.6.2 do-while
语句
do-while
语句是一种后测试循环语句,即循环体中的代码执行后才会对退出条件进行求值,即循环体内的代码至少执行一次。
do-while
语法:
do {
statement
} while (expression);
例
let i = 0;
do {
i += 2;
} while (i < 10);
注意 后测试循环经常用于这种情形:循环体内代码在退出前至少要执行一次。
3.6.3 while
语句
while
语句是一种先测试循环语句,即先检测退出条件,再执行循环体内的代码。因此,while 循环体内的代码有可能不会执行。
while
语法 while(expression) statement
例
let i = 0;
while (i < 10) {
i += 2;
}
3.6.4 for
语句
for 语句也是先测试语句,先检测退出条件expression
,再执行循环体内的代码statement
,只不过增加了进入循环之前的初始化代码initialization
,以及循环执行后要执行的表达式post-loop-expression
。
语法为:for (initialization; expression; post-loop-expression) statement
例:
for
循环
let count = 10;
for (let i = 0; i < count; i++) {
console.log(i);
}
等价于 while
循环
let count = 10;
let i = 0;
while (i < count) {
console.log(i);
i++;
}
以上代码在循环开始前定义了变量 i
的初始值为 0。然后求值条件表达式,如果求值结果为 true
(i < count
),则执行循环体。因此循环体也可能不会被执行。如果循环体被执行了,则循环后表达式i++
也会执行,以便递增变量 i
。
无法通过 while 循环实现的逻辑,同样也无法使用 for 循环实现。因此 for 循环只是将循环相关的代码封装在了一起而已。
在 for 循环的初始化代码中,其实是可以不使用变量声明关键字的。不过,初始化定义的迭代器变量在循环执行完成后几乎不可能再用到了。因此,最清晰的写法是使用 let 声明迭代器变量,这样就可以将这个变量的作用域限定在循环中。
初始化、条件表达式和循环后表达式都不是必需的。因此,下面这种写法可以创建一个无穷循环。
for (;;) { // 无穷循环
doSomething();
}
如果只包含条件表达式,那么 for 循环实际上就变成了 while 循环:
let count = 10;
let i = 0;
for (; i < count; ) {
console.log(i);
i++;
}
因此,for 循环具有多功能性。
3.6.5 for-in
语句
for-in
语句是一种严格的迭代语句,用于枚举对象中的非符号键属性
。
语法如下:for (property in expression) statement
这个例子使用 for-in
循环显示了 BOM
对象 window
的所有属性。每次执行循环,都会给变量 propName 赋予一个 window
对象的属性作为值,直到 window
的所有属性都被枚举一遍。
与 for
循环一样,这里控制语句中的 const
也不是必需的。但为了确保这个局部变量不被修改,推荐使用 const
。
ECMAScript 中对象的属性是无序的,因此 for-in
语句不能保证返回对象属性的顺序。换句话说,所有可枚举的属性都会返回一次,但返回的顺序可能会因浏览器而异。
如果 for-in
循环要迭代的变量是 null
或 undefined
,则不执行循环体。
3.6.6 for-of
语句
for-of
语句是一种严格的迭代语句,用于遍历可迭代对象的元素。
语法如下:for (property of expression) statement
上例使用 for-of
语句显示了一个包含 4 个元素的数组中的所有元素。循环会一直持续到将所有元素都迭代完。
与 for
循环一样,这里控制语句中的 const 也不是必需的。但为了确保这个局部变量不被修改,推荐使用 const。
for-of
循环会按照可迭代对象的 next()
方法产生值的顺序迭代元素。(关于可迭代对象,将在第 7 章详细介绍)
如果尝试迭代的变量不支持迭代,则 for-of
语句会抛出错误。
3.6.7 标签语句
标签语句用于给语句加标签,典型应用场景是嵌套循环。
语法为:label: statement
start: for (let i = 0; i < count; i++) {
console.log(i);
}
在这个例子中,start 是一个标签,可以在后面通过 break
或 continue
语句引用。
3.6.8 break
和 continue
语句
break
和 continue
语句为执行循环代码提供了更严格的控制手段。
1、break
语句用于立即退出循环,强制执行循环后的下一条语句。
continue
语句也用于立即退出循环,是退出当前次的循环, 会再次从循环顶部开始执行下一次。
let num = 0;
for (let i = 1; i < 10; i++) {
console.log('i=', i);
if (i % 5 == 0) {
console.log('i% 5 == 0, i=', i);
break;
}
num++;
console.log('num=', num)
}
console.log('lastNum=', num); // 4
在上面的代码中,for
循环会将变量 i
由 1 递增到 10。而在循环体内,有一个 if
语句用于检查 i
能否被 5 整除(使用取模操作符)。如果是,则执行 break
语句,退出循环。变量 num 的初始值为 0,表示循环在退出前执行了多少次。当 break
语句执行后,下一行执行的代码是 console.log(num)
,显示 4。之所以循环执行了 4 次,是因为当 i
等于 5 时,break 语句会导致当前for
循环退出,该次循环不会执行递增 num 的代码。
let num = 0;
for (let i = 1; i < 10; i++) {
console.log('i=', i);
if (i % 5 == 0) {
console.log('i% 5 == 0, i=', i);
continue;
}
num++;
console.log('num=', num)
}
console.log('lastNum=', num); // 8
一共9次循环,完整执行了 8 次。当 i
等于 5 时,循环会在递增num
之前退出,但会执行下一次迭代,此时i
是 6。然后,循环会一直执行到自然结束,即i
等于 10 时结束循环。最终num
的值是 8 而不是 9,是因为 continue
语句导致它少递增了一次。
注意, break 跳出循环, 在不加标签的情况下, 是指跳出其当前的循环体。如下例中, break跳出循环,是跳出break所在的 变量 j 的 for 循环,但外层 变量 i 的for循环正常继续。
continue
退出当前次的循环, 会再次从当前循环顶部开始执行下一次循环体。也只针对当前循环, 外层 变量 i 的for 循环正常进行。
let numJ = 0;
let numI = 0;
for (let i = 0; i < 3; i++) {
for (let j = 0; j < 3; j++) {
if (i == 1 && j == 1) {
continue;
// break;
}
numJ++;
console.log("numJ, i, j=",numJ, i, j);
}
numI ++;
console.log("numI, i=",numI, i);
}
console.log('numJ=', numJ);
console.log('numI=', numI);
2、break
和 continue
都可以与标签语句一起使用,返回代码中特定的位置。
let num = 0;
outermost:
for (let i = 0; i < 3; i++) {
for (let j = 0; j < 3; j++) {
if (i == 1 && j == 1) {
break outermost;
}
num++;
console.log("num, i, j=",num, i, j);
}
}
console.log(num); // 4
标签outermost标识的是一个for循环语句
外层 i 从 0 到 2,内层 j 从 0 到 2,正常情况下,num++ 语句会执行 3*3 = 9
次,循环结束时,num=9。
但是,break outermost
语句要退出到标签outermost。添加标签不仅让 break 退出(使用变量 j 的)内部循环,也会退出(使用变量 i 的)外部循环。当执行到 i 和 j 都等于 1 时,循环break outermost
停止执行,此时 num 的值是 4。
i = 0 时,j 从 0 到 2,完整执行循环 1*3= 3次
i = 1 时,j 在加到1时 执行break,跳出循环,退到标签outermost,仅完整执行了 i = 1 j = 0 时的1次num++, 因此 num 最终为 4.
let num = 0;
outermost:
for (let i = 0; i < 3; i++) {
for (let j = 0; j < 3; j++) {
if (i == 1 && j == 1) {
continue outermost;
}
num++;
console.log("num, i, j=",num, i, j);
}
}
console.log(num);
这一次,continue
语句会强制循环继续执行,但不是继续执行变量j
的内部循环。
因为加了标签continue outermost
, 返回到 outermost 位置处继续执行变量i
的外部循环。
当 i 和 j 都等于 1 时,会执行 continue outermost
,跳到变量i
的外部循环继续执行 i = 2 时的循环,从而导致内部循环少执行 2 次 (i=1时的 j=1, j=2 这两次),结果 num 等于 7。
组合使用标签
语句和 break
、continue
能实现复杂的逻辑,但也容易出错。注意标签要使用描述性强的文本,而嵌套也不要太深。
3.6.9 with
语句(不推荐使用)
3.6.10 switch
语句
switch 语句是与 if 语句紧密相关的一种流控制语句, 语法如下:
switch (expression) {
case value1:
statement
break;
case value2:
statement
break;
case value3:
statement
break;
case value4:
statement
break;
default:
statement
}
每个 case
(条件/分支)相当于:“如果表达式等于后面的值,则执行下面的语句。”
break
关键字会导致代码执行跳出 switch
语句。如果没有 break
,则代码会继续匹配下一个条件。
default
关键字用于在任何条件都没有满足时指定默认执行的语句(相当于 else
语句)
为避免不必要的条件判断,最好给每个条件后面都加上 break
语句。如果确实需要连续匹配几个条件,那么推荐写个注释表明是故意忽略了 break
.
let i = 25;
switch (i) {
case 25:
/*跳过*/
case 35:
console.log("25 or 35");
break;
case 45:
console.log("45");
break;
default:
console.log("Other");
}
switch
语句是从其他语言借鉴过来的,但 ECMAScript 为它赋予了一些独有的特性。首先,switch
语句可以用于所有数据类型(在很多语言中,它只能用于数值),因此可以使用字符串甚至对象。其次,case条件的值不需要是常量,也可以是变量或表达式。
这个例子在 switch
语句中使用了字符串。第一个条件实际上使用的是表达式,求值为两个字符串拼接后的结果。因为拼接后的结果等于 switch
的参数,所以 console.log
会输出"Greeting was found."。
上面的代码首先在外部定义了变量 num,而传给 switch
语句的参数之所以是 true
,就是因为每个case
条件的表达式(num < 0, num >= 0 && num <= 10, num > 10 && num <= 20 )都会返回布尔值。条件的表达式分别被求值,直到有表达式返回 true
;否则,就会一直跳到 default
语句(这个例子正是如此)。
如果传给switch的参数是false
, 则在条件的表达式返回 false
时, 执行该条件下对应的语句, 如下示例:
注意 switch 语句在比较每个条件的值时会使用全等操作符,因此不会强制转换数据类型(比如,字符串"10"不等于数值 10)。
3.7 函数
函数对任何语言来说都是核心组件,因为它们可以封装语句,然后在任何地方、任何时间执行。
ECMAScript 中的函数使用 function
关键字声明,后跟一组参数,然后是函数体。
函数的基本语法:
function functionName(arg0, arg1,...,argN) {
statements
}
1、ECMAScript 中的函数不需要指定是否返回值。
任何函数在任何时间都可以使用 return
语句来返回任何值,用法是后跟要返回的值。不指定返回值的函数实际上会返回特殊值 undefined
。
只要碰到 return
语句,函数就会立即停止执行并退出。因此,return
语句后面的代码不会被执行。
function sum(num1, num2) {
return num1 + num2;
console.log("Hello world"); // 不会执行
}
示例函数 sum()
会将两个值相加并返回结果。注意,除了 return
语句之外没有任何特殊声明表明该函数有返回值, 调用方法:
const result = sum(5, 10);
2、一个函数里也可以有多个 return
语句,像这样:
function diff(num1, num2) {
if (num1 < num2) {
return num2 - num1;
} else {
return num1 - num2;
}
}
这个 diff()
函数用于计算两个数值的差。如果第一个数值小于第二个,则用第二个减第一个;否则,就用第一个减第二个。代码中每个分支都有自己的 return 语句,返回正确的差值。
3、return
语句也可以不带返回值。这时候,函数会立即停止执行并返回 undefined
。这种用法最常用于提前终止函数执行,并不是为了返回值。
function sayHi(name, message) {
return;
console.log("Hello " + name + ", " + message); // 不会执行
}
注意:最佳实践是函数要么返回值,要么不返回值。只在某个条件下返回值的函数会带来麻烦,尤其是调试时
严格模式对函数也有一些限制:
函数不能以 eval 或 arguments 作为名称;
函数的参数不能叫 eval 或 arguments;
两个命名参数不能拥有同一个名称。
如果违反上述规则,则会导致语法错误,代码也不会执行。