第三章——语言基础(下)操作符、语句、函数

3.5 操作符

        ECMA-262描述了一组可用于操作数据值的操作符,包括数学操作符(如加、减)、位操作符、关系操作符和相等操作符等。ECMAScript中的操作符是独特的,因为它们可用于各种值,包括字符串、数值、布尔值,甚至还有对象。在应用给对象时,操作符通常会调用valueOf()和/或toString()方法来取得可以计算的值。

3.5.1 一元操作符

        只操作一个值的操作符叫一元操作符(unary operator)。一元操作符是ECMAScript中最简单的操作符。

1、递增/递减操作符

        递增和递减操作符直接照搬自C语言,但有两个版本:前缀版和后缀版。顾名思义,前缀版就是位于要操作的变量前头,后缀版就是位于要操作的变量后头。前缀递增操作符会给数值加1,把两个加号(++)放到变量前头即可:

let age = 29;
++age;

        在这个例子中,前缀递增操作符把age的值变成了30(给之前的值29加1)。因此,它实际上等于如下表达式:

let age = 29;
age = age + 1;

        前缀递减操作符也类似,只不过是从一个数值减1。使用前缀递减操作符,只要把两个减号(--)放到变量前头即可:

let age = 29;
--age;

        执行操作后,变量age的值变成了28(从29减1)。无论使用前缀递增还是前缀递减操作符,变量的值都会在语句被求值之前改变。(在计算机科学中,这通常被称为具有副作用。)请看下面的例子:

let age = 29;
let anotherAge = --age + 2;
console.log(age); // 28
console.log(anotherAge); // 30

        在这个例子中,变量anotherAge以age减1后的值再加2进行初始化。因为递减操作先发生,所以age的值先变成28,然后再加2,结果是30。
        前缀递增和递减在语句中的优先级是相等的,因此会从左到右依次求值。比如:

let num1 = 2;
let num2 = 20;
let num3 = --num1 + num2;
let num4 = num1 + num2;
console.log(num3); // 21
console.log(num4); // 21

        递增和递减的后缀版语法一样(分别是++和--),只不过要放在变量后面。后缀版与前缀版的主要区别在于,后缀版递增和递减在语句被求值后才发生。在某些情况下,这种差异没什么影响,比如:

let age = 29;
age++;

        把递增操作符放到变量后面不会改变语句执行的结果,因为递增是唯一的操作。可是,在跟其他操作混合时,差异就会变明显,比如:

let num1 = 2;
let num2 = 20;
let num3 = num1-- + num2;
let num4 = num1 + num2;
console.log(num3); // 22
console.log(num4); // 21

        这个例子跟前面的那个一样,只是把前缀递减改成了后缀递减,区别很明显。在使用前缀版的例子中,num3和num4的值都是21。而在这个例子中,num3的值是22,num4的值是21。这里的不同之处在于,计算num3时使用的是num1的原始值(2),而计算num4时使用的是num1递减后的值(1)。
        这4个操作符可以作用于任何值,意思是不限于整数——字符串、布尔值、浮点值,甚至对象都可以。递增和递减操作符遵循如下规则。

  • 对于字符串,如果是有效的数值形式,则转换为数值再应用改变。变量类型从字符串变成数值。
  • 对于字符串,如果不是有效的数值形式,则将变量的值设置为NaN。变量类型从字符串变成数值。
  • 对于布尔值,如果是false,则转换为0再应用改变。变量类型从布尔值变成数值。
  • 对于布尔值,如果是true,则转换为1再应用改变。变量类型从布尔值变成数值。
  • 对于浮点值,加1或减1。
  • 如果是对象,则调用其valueOf()方法取得可以操作的值。对得到的值应用上述规则。如果是NaN,则调用toString()并再次应用其他规则。变量类型从对象变成数值。

下面的例子演示了这些规则:

let s1 = "2";
let s2 = "z";
let b = false;
let f = 1.1;
let o = {
    valueOf() {
        return -1;
    }
};
s1++; // 值变成数值3
s2++; // 值变成NaN
b++; // 值变成数值1
f--; // 值变成0.10000000000000009(因为浮点数不精确)
o--; // 值变成-2

2、一元加和减

        一元加和减操作符对大多数开发者来说并不陌生,它们在ECMAScript中跟在高中数学中的用途一样。一元加由一个加号(+)表示,放在变量前头,对数值没有任何影响:

let num = 25;
num = +num;
console.log(num); // 25

        如果将一元加应用到非数值,则会执行与使用Number()转型函数一样的类型转换:布尔值false和true转换为0和1,字符串根据特殊规则进行解析,对象会调用它们的valueOf()和/或toString()方法以得到可以转换的值。

        下面的例子演示了一元加在应用到不同数据类型时的行为:

let s1 = "01";
let s2 = "1.1";
let s3 = "z";
let b = false;
let f = 1.1;
let o = {
    valueOf() {
        return -1;
    }
};
s1 = +s1; // 值变成数值1
s2 = +s2; // 值变成数值1.1
s3 = +s3; // 值变成NaN
b = +b; // 值变成数值0
f = +f; // 不变,还是1.1
o = +o; // 值变成数值-1


        一元减由一个减号(-)表示,放在变量前头,主要用于把数值变成负值,如把1转换为-1。示例如下:

let num = 25;
num = -num;
console.log(num); // -25

        对数值使用一元减会将其变成相应的负值(如上面的例子所示)。在应用到非数值时,一元减会遵循与一元加同样的规则,先对它们进行转换,然后再取负值:

let s1 = "01";
let s2 = "1.1";
let s3 = "z";
let b = false;
let f = 1.1;
let o = {
    valueOf() {
        return -1;
    }
};
s1 = -s1; // 值变成数值-1
s2 = -s2; // 值变成数值-1.1
s3 = -s3; // 值变成NaN
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的幂。第一位(称为第0位)表示2的0次幂,第二位表示2的1次幂,依此类推。如果一个位是空的,则以0填充,相当于忽略不计。比如,数值18的二进制格式为00000000000000000000000000010010,或更精简的10010。后者是用到的5个有效位,决定了实际的值(如下图所示)。

        负值以一种称为二补数(或补码)的二进制编码存储。一个数值的二补数通过如下3个步骤计算得到:
(1) 确定绝对值的二进制表示(如,对于-18,先确定18的二进制表示);
(2) 找到数值的一补数(或反码),换句话说,就是每个0都变成1,每个1都变成0;
(3) 给结果加1。
        基于上述步骤确定-18的二进制表示,首先从18的二进制表示开始:

0000 0000 0000 0000 0000 0000 0001 0010

        然后,计算一补数,即反转每一位的二进制值:

1111 1111 1111 1111 1111 1111 1110 1101

        最后,给一补数加1:

1111 1111 1111 1111 1111 1111 1110 1101
                                      1
----------------------------------------------
1111 1111 1111 1111 1111 1111 1110 1110

        那么,-18的二进制表示就是11111111111111111111111111101110。要注意的是,在处理有符号整数时,我们无法访问第31位。
        ECMAScript会帮我们记录这些信息。在把负值输出为一个二进制字符串时,我们会得到一个前面加了减号的绝对值,如下所示:

let num = -18;
console.log(num.toString(2)); // "-10010"

        在将-18转换为二进制字符串时,结果得到-10010。转换过程会求得二补数,然后再以更符合逻辑的形式表示出来。

注意       默认情况下,ECMAScript中的所有整数都表示为有符号数。不过,确实存在无符号整数。对无符号整数来说,第32位不表示符号,因为只有正值。无符号整数比有符号整数的范围更大,因为符号位被用来表示数值了。

        在对ECMAScript中的数值应用位操作符时,后台会发生转换:64位数值会转换为32位数值,然后执行位操作,最后再把结果从32位转换为64位存储起来。整个过程就像处理32位数值一样,这让二进制操作变得与其他语言中类似。但这个转换也导致了一个奇特的副作用,即特殊值NaN和Infinity在位操作中都会被当成0处理。
        如果将位操作符应用到非数值,那么首先会使用Number()函数将该值转换为数值(这个过程是自动的),然后再应用位操作。最终结果是数值。

1.按位非

        按位非操作符用波浪符(~)表示,它的作用是返回数值的一补数。按位非是ECMAScript中为数不多的几个二进制数学操作符之一。看下面的例子:

let num1 = 25; // 二进制00000000000000000000000000011001
let num2 = ~num1; // 二进制11111111111111111111111111100110
console.log(num2); // -26

        这里,按位非操作符作用到了数值25,得到的结果是-26。由此可以看出,按位非的最终效果是对数值取反并减1,就像执行如下操作的结果一样:

let num1 = 25;
let num2 = -num1 - 1;
console.log(num2); // "-26"

        实际上,尽管两者返回的结果一样,但位操作的速度快得多。这是因为位操作是在数值的底层表示上完成的。

2.按位与

        按位与操作符用和号(&)表示,有两个操作数。本质上,按位与就是将两个数的每一个位对齐,然后基于真值表中的规则,对每一位执行相应的与操作。

        按位与操作在两个位都是1时返回1,在任何一位是0时返回0。
        下面看一个例子,我们对数值25和3求与操作,如下所示: 

let result = 25 & 3;
console.log(result); // 1

        25和3的按位与操作的结果是1。为什么呢?看下面的二进制计算过程:

 25 = 0000 0000 0000 0000 0000 0000 0001 1001
  3 = 0000 0000 0000 0000 0000 0000 0000 0011
---------------------------------------------
AND = 0000 0000 0000 0000 0000 0000 0000 0001

        如上所示,25和3的二进制表示中,只有第0位上的两个数都是1。于是结果数值的所有其他位都会以0填充,因此结果就是1。

3.按位或

        按位或操作符用管道符(|)表示,同样有两个操作数。按位或遵循如下真值表:

        按位或操作在至少一位是1时返回1,两位都是0时返回0。
        仍然用按位与的示例,如果对25和3执行按位或,代码如下所示:

let result = 25 | 3;
console.log(result); // 27

        可见25和3的按位或操作的结果是27:

25 = 0000 0000 0000 0000 0000 0000 0001 1001
 3 = 0000 0000 0000 0000 0000 0000 0000 0011
---------------------------------------------
OR = 0000 0000 0000 0000 0000 0000 0001 1011

        在参与计算的两个数中,有4位都是1,因此它们直接对应到结果上。二进制码11011等于27。

4.按位异或

        按位异或用脱字符(^)表示,同样有两个操作数。下面是按位异或的真值表:

        按位异或与按位或的区别是,它只在一位上是1的时候返回1(两位都是1或0,则返回0)。
        对数值25和3执行按位异或操作:

let result = 25 ^ 3;
console.log(result); // 26

        可见,25和3的按位异或操作结果为26,如下所示:

 25 = 0000 0000 0000 0000 0000 0000 0001 1001
  3 = 0000 0000 0000 0000 0000 0000 0000 0011
---------------------------------------------
XOR = 0000 0000 0000 0000 0000 0000 0001 1010

        两个数在4位上都是1,但两个数的第0位都是1,因此那一位在结果中就变成了0。其余位上的1在另一个数上没有对应的1,因此会直接传递到结果中。二进制码11010等于26。(注意,这比对同样两个值执行按位或操作得到的结果小1。)

5.左移

        左移操作符用两个小于号(<<)表示,会按照指定的位数将数值的所有位向左移动。比如,如果数值2(二进制10)向左移5位,就会得到64(二进制1000000),如下所示:

let oldValue = 2; // 等于二进制10
let newValue = oldValue << 5; // 等于二进制1000000,即十进制64

        注意在移位后,数值右端会空出5位。左移会以0填充这些空位,让结果是完整的32位数值(见下图)。

        注意,左移会保留它所操作数值的符号。比如,如果-2左移5位,将得到-64,而不是正64。

6.有符号右移

        有符号右移由两个大于号(>>)表示,会将数值的所有32位都向右移,同时保留符号(正或负)。有符号右移实际上是左移的逆运算。比如,如果将64右移5位,那就是2:

let oldValue = 64; // 等于二进制1000000
let newValue = oldValue >> 5; // 等于二进制10,即十进制2

        同样,移位后就会出现空位。不过,右移后空位会出现在左侧,且在符号位之后(见下图)。ECMAScript会用符号位的值来填充这些空位,以得到完整的数值。

7.无符号右移

        无符号右移用3个大于号表示(>>>),会将数值的所有32位都向右移。对于正数,无符号右移与有符号右移结果相同。仍然以前面有符号右移的例子为例,64向右移动5位,会变成2:

let oldValue = 64; // 等于二进制1000000
let newValue = oldValue >>> 5; // 等于二进制10,即十进制2

        对于负数,有时候差异会非常大。与有符号右移不同,无符号右移会给空位补0,而不管符号位是什么。对正数来说,这跟有符号右移效果相同。但对负数来说,结果就差太多了。无符号右移操作符将负数的二进制表示当成正数的二进制表示来处理。因为负数是其绝对值的二补数,所以右移之后结果变得非常之大,如下面的例子所示:

let oldValue = -64; // 等于二进制11111111111111111111111111000000
let newValue = oldValue >>> 5; // 等于十进制134217726

       在对-64无符号右移5位后,结果是134 217 726。这是因为-64的二进制表示是11111111111111111111111111000000,无符号右移却将它当成正值,也就是4 294 967 232。把这个值右移5位后,结果是00000111111111111111111111111110,即134 217 726。

3.5.3 布尔操作符

        对于编程语言来说,布尔操作符跟相等操作符几乎同样重要。如果没有能力测试两个值的关系,那么像if-else和循环这样的语句也没什么用了。布尔操作符一共有3个:逻辑非、逻辑与和逻辑或。

1.逻辑非

        逻辑非操作符由一个叹号(!)表示,可应用给ECMAScript中的任何值。这个操作符始终返回布尔值,无论应用到的是什么数据类型。逻辑非操作符首先将操作数转换为布尔值,然后再对其取反。换句话说,逻辑非操作符会遵循如下规则。

  • 如果操作数是对象,则返回false。
  • 如果操作数是空字符串,则返回true。
  • 如果操作数是非空字符串,则返回false。
  • 如果操作数是数值0,则返回true。
  • 如果操作数是非0数值(包括Infinity),则返回false。
  • 如果操作数是null,则返回true。
  • 如果操作数是NaN,则返回true。
  • 如果操作数是undefined,则返回true。

        以下示例验证了上述行为:

console.log(!false); // true
console.log(!"blue"); // false
console.log(!0); // true
console.log(!NaN); // true
console.log(!""); // true
console.log(!12345); // false

        逻辑非操作符也可以用于把任意值转换为布尔值。同时使用两个叹号(!!),相当于调用了转型函数Boolean()。无论操作数是什么类型,第一个叹号总会返回布尔值。第二个叹号对该布尔值取反,从而给出变量真正对应的布尔值。结果与对同一个值使用Boolean()函数是一样的:

console.log(!!"blue"); // true
console.log(!!0); // false
console.log(!!NaN); // false
console.log(!!""); // false
console.log(!!12345); // true

2.逻辑与

        逻辑与操作符由两个和号(&&)表示,应用到两个值,如下所示:

let result = true && false;

        逻辑与操作符遵循如下真值表:

        逻辑与操作符可用于任何类型的操作数,不限于布尔值。如果有操作数不是布尔值,则逻辑与并不一定会返回布尔值,而是遵循如下规则。

  • 如果第一个操作数是对象,则返回第二个操作数。
  • 如果第二个操作数是对象,则只有第一个操作数求值为true才会返回该对象。
  • 如果两个操作数都是对象,则返回第二个操作数。
  • 如果有一个操作数是null,则返回null。
  • 如果有一个操作数是NaN,则返回NaN。
  • 如果有一个操作数是undefined,则返回undefined。

        逻辑与操作符是一种短路操作符,意思就是如果第一个操作数决定了结果,那么永远不会对第二个操作数求值。对逻辑与操作符来说,如果第一个操作数是false,那么无论第二个操作数是什么值,结果也不可能等于true。看下面的例子:

let found = true;
let result = (found && someUndeclaredVariable); // 这里会出错
console.log(result); // 不会执行这一行

        上面的代码之所以会出错,是因为someUndeclaredVariable没有事先声明,所以当逻辑与操作符对它求值时就会报错。变量found的值是true,逻辑与操作符会继续求值变量
someUndeclaredVariable。但是由于someUndeclaredVariable没有定义,不能对它应用逻辑与操作符,因此就报错了。假如变量found的值是false,那么就不会报错了:

let found = false;
let result = (found && someUndeclaredVariable); // 不会出错
console.log(result); // 会执行

        这里,console.log会成功执行。即使变量someUndeclaredVariable没有定义,由于第一个操作数是false,逻辑与操作符也不会对它求值,因为此时对&&右边的操作数求值是没有意义的。在使用逻辑与操作符时,一定别忘了它的这个短路的特性。

3.逻辑或

        逻辑或操作符由两个管道符(||)表示,比如:

let result = true || false;

        逻辑或操作符遵循如下真值表:

        与逻辑与类似,如果有一个操作数不是布尔值,那么逻辑或操作符也不一定返回布尔值。它遵循如下规则。 

  • 如果第一个操作数是对象,则返回第一个操作数。
  • 如果第一个操作数求值为false,则返回第二个操作数。
  • 如果两个操作数都是对象,则返回第一个操作数。
  • 如果两个操作数都是null,则返回null。
  • 如果两个操作数都是NaN,则返回NaN。
  • 如果两个操作数都是undefined,则返回undefined。

        同样与逻辑与类似,逻辑或操作符也具有短路的特性。只不过对逻辑或而言,第一个操作数求值为true,第二个操作数就不会再被求值了。看下面的例子:

let found = true;
let result = (found || someUndeclaredVariable); // 不会出错
console.log(result); // 会执行

        跟前面的例子一样,变量someUndeclaredVariable也没有定义。但是,因为变量found的值为true,所以逻辑或操作符不会对变量someUndeclaredVariable求值,而直接返回true。假如把found的值改为false,那就会报错了:

let found = false;
let result = (found || someUndeclaredVariable); // 这里会出错
console.log(result); // 不会执行这一行

        利用这个行为,可以避免给变量赋值null或undefined。比如:

let myObject = preferredObject || backupObject;

        在这个例子中,变量myObject会被赋予两个值中的一个。其中,preferredObject变量包含首选的值,backupObject变量包含备用的值。如果preferredObject不是null,则它的值就会赋给
myObject;如果preferredObject是null,则backupObject的值就会赋给myObject。这种模式在ECMAScript代码中经常用于变量赋值。

3.5.4 乘性操作符

        ECMAScript定义了3个乘性操作符:乘法、除法和取模。这些操作符跟它们在Java、C语言及Perl中对应的操作符作用一样,但在处理非数值时,它们也会包含一些自动的类型转换。如果乘性操作符有不是数值的操作数,则该操作数会在后台被使用Number()转型函数转换为数值。这意味着空字符串会被当成0,而布尔值true会被当成1。

1.乘法操作符

        乘法操作符由一个星号(*)表示,可以用于计算两个数值的乘积。其语法类似于C语言,比如:

let result = 34 * 56;

        不过,乘法操作符在处理特殊值时也有一些特殊的行为。

  • 如果操作数都是数值,则执行常规的乘法运算,即两个正值相乘是正值,两个负值相乘也是正值,正负符号不同的值相乘得到负值。如果ECMAScript不能表示乘积,则返回Infinity或-Infinity。
  • 如果有任一操作数是NaN,则返回NaN。
  • 如果是Infinity乘以0,则返回NaN。
  • 如果是Infinity乘以非0的有限数值,则根据第二个操作数的符号返回Infinity或-Infinity。
  • 如果是Infinity乘以Infinity,则返回Infinity。
  • 如果有不是数值的操作数,则先在后台用Number()将其转换为数值,然后再应用上述规则。

2.除法操作符

        除法操作符由一个斜杠(/)表示,用于计算第一个操作数除以第二个操作数的商,比如:

let result = 66 / 11;

        跟乘法操作符一样,除法操作符针对特殊值也有一些特殊的行为。

  • 如果操作数都是数值,则执行常规的除法运算,即两个正值相除是正值,两个负值相除也是正值,符号不同的值相除得到负值。如果ECMAScript不能表示商,则返回Infinity或-Infinity。
  • 如果有任一操作数是NaN,则返回NaN。
  • 如果是Infinity除以Infinity,则返回NaN。
  • 如果是0除以0,则返回NaN。
  • 如果是非0的有限值除以0,则根据第一个操作数的符号返回Infinity或-Infinity。
  • 如果是Infinity除以任何数值,则根据第二个操作数的符号返回Infinity或-Infinity。
  • 如果有不是数值的操作数,则先在后台用Number()函数将其转换为数值,然后再应用上述规则。

3.取模操作符

        取模(余数)操作符由一个百分比符号(%)表示,比如:

let result = 26 % 5; // 等于1

        与其他乘性操作符一样,取模操作符对特殊值也有一些特殊的行为。

  • 如果操作数是数值,则执行常规除法运算,返回余数。
  • 如果被除数是无限值,除数是有限值,则返回NaN。
  • 如果被除数是有限值,除数是0,则返回NaN。
  • 如果是Infinity除以Infinity,则返回NaN。
  • 如果被除数是有限值,除数是无限值,则返回被除数。
  • 如果被除数是0,除数不是0,则返回0。
  • 如果有不是数值的操作数,则先在后台用Number()函数将其转换为数值,然后再应用上述规则。

3.5.5 指数操作符

        ECMAScript 7新增了指数操作符,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

        不仅如此,指数操作符也有自己的指数赋值操作符**=,该操作符执行指数运算和结果的赋值操作:

let squared = 3;
squared **= 2;
console.log(squared); // 9

let sqrt = 16;
sqrt **= 0.5;
console.log(sqrt); // 4

3.5.6 加性操作符

        加性操作符,即加法和减法操作符,一般都是编程语言中最简单的操作符。不过,在ECMAScript中,这两个操作符拥有一些特殊的行为。与乘性操作符类似,加性操作符在后台会发生不同数据类型的转换。只不过对这两个操作符来说,转换规则不是那么直观。

1.加法操作符

        加法操作符(+)用于求两个数的和,比如:

let result = 1 + 2;

        如果两个操作数都是数值,加法操作符执行加法运算并根据如下规
则返回结果:

  • 如果有任一操作数是NaN,则返回NaN;
  • 如果是Infinity加Infinity,则返回Infinity;
  • 如果是-Infinity加-Infinity,则返回-Infinity;
  • 如果是Infinity加-Infinity,则返回NaN;
  • 如果是+0加+0,则返回+0;
  • 如果是-0加+0,则返回+0;
  • 如果是-0加-0,则返回-0。

        不过,如果有一个操作数是字符串,则要应用如下规则:

  • 如果两个操作数都是字符串,则将第二个字符串拼接到第一个字符串后面;
  • 如果只有一个操作数是字符串,则将另一个操作数转换为字符串,再将两个字符串拼接在一起。

        如果有任一操作数是对象、数值或布尔值,则调用它们的toString()方法以获取字符串,然后再应用前面的关于字符串的规则。对于undefined和null,则调用String()函数,分别获取"undefined"和"null"。
        看下面的例子:

let result1 = 5 + 5; // 两个数值
console.log(result1); // 10
let result2 = 5 + "5"; // 一个数值和一个字符串
console.log(result2); // "55"

        以上代码展示了加法操作符的两种运算模式。正常情况下,5 + 5等于10(数值),如前两行代码所示。但是,如果将一个操作数改为字符串,比如"5",则相加的结果就变成了"55"(原始字符串值),因为第一个操作数也会被转换为字符串。
        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"

        这里,变量message中保存的是一个字符串,是执行两次加法操作之后的结果。有人可能会认为最终得到的字符串是"The sum of 5and 10 is 15"。可是,实际上得到的是"The sum of 5 and 10 is 510"。这是因为每次加法运算都是独立完成的。第一次加法的操作数是一个字符串和一个数值(5),结果还是一个字符串。第二次加法仍然是用一个字符串去加一个数值(10),同样也会得到一个字符串。如果想真正执行数学计算,然后把结果追加到字符串末尾,只要使用一对括号即可:

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 15"

        在此,我们用括号把两个数值变量括了起来,意思是让解释器先执行两个数值的加法,然后再把结果追加给字符串。因此,最终得到的字符串变成了"The sum of 5 and 10 is 15"。

2.减法操作符

        减法操作符(-)也是使用很频繁的一种操作符,比如:

let result = 2 - 1;

        与加法操作符一样,减法操作符也有一组规则用于处理ECMAScript中不同类型之间的转换。

  • 如果两个操作数都是数值,则执行数学减法运算并返回结果。
  • 如果有任一操作数是NaN,则返回NaN。
  • 如果是Infinity减Infinity,则返回NaN。
  • 如果是-Infinity减-Infinity,则返回NaN。
  • 如果是Infinity减-Infinity,则返回Infinity。
  • 如果是-Infinity减Infinity,则返回-Infinity。
  • 如果是+0减+0,则返回+0。
  • 如果是+0减-0,则返回-0。
  • 如果是-0减-0,则返回+0。
  • 如果有任一操作数是字符串、布尔值、null或undefined,则先在后台使用Number()将其转换为数值,然后再根据前面的规则执行数学运算。如果转换结果是NaN,则减法计算的结果是NaN。
  • 如果有任一操作数是对象,则调用其valueOf()方法取得表示它的数值。如果该值是NaN,则减法计算的结果是NaN。如果对象没有valueOf()方法,则调用其toString()方法,然后再将得到的字符串转换为数值。

以下示例演示了上面的规则:

let result1 = 5 - true; // true被转换为1,所以结果是4
let result2 = NaN - 1; // NaN
let result3 = 5 - 3; // 2
let result4 = 5 - ""; // ""被转换为0,所以结果是5
let result5 = 5 - "2"; // "2"被转换为2,所以结果是3
let result6 = 5 - null; // null被转换为0,所以结果是5

3.5.7 关系操作符

        关系操作符执行比较两个值的操作,包括小于(<)、大于(>)、小于等于(<=)和大于等于(>=),用法跟数学课上学的一样。这几个操作符都返回布尔值,如下所示:

let result1 = 5 > 3; // true
let result2 = 5 < 3; // false

        与ECMAScript中的其他操作符一样,在将它们应用到不同数据类型时也会发生类型转换和其他行为。

  • 如果操作数都是数值,则执行数值比较。
  • 如果操作数都是字符串,则逐个比较字符串中对应字符的编码。
  • 如果有任一操作数是数值,则将另一个操作数转换为数值,执行数值比较。
  • 如果有任一操作数是对象,则调用其valueOf()方法,取得结果后再根据前面的规则执行比较。如果没有valueOf()操作符,则调用toString()方法,取得结果后再根据前面的规则执行比较。
  • 如果有任一操作数是布尔值,则将其转换为数值再执行比较。

        在使用关系操作符比较两个字符串时,会发生一个有趣的现象。很多人认为小于意味着“字母顺序靠前”,而大于意味着“字母顺序靠后”,实际上不是这么回事。对字符串而言,关系操作符会比较字符串中对应字符的编码,而这些编码是数值。比较完之后,会返回布尔值。问题的关键在于,大写字母的编码都小于小写字母的编码,因此以下这种情况就会发生:

let result = "Brick" < "alphabet"; // true

        在这里,字符串"Brick"被认为小于字符串"alphabet",因为字母B的编码是66,字母a的编码是97。要得到确实按字母顺序比较的结果,就必须把两者都转换为相同的大小写形式(全大写或全小写),然后再比较:

let result = "Brick".toLowerCase() < "alphabet".toLowerCase(); // false

        将两个操作数都转换为小写,就能保证按照字母表顺序判定"alphabet"在"Brick"前头。
另一个奇怪的现象是在比较两个数值字符串的时候,比如下面这个例子:

let result = "23" < "3"; // true

        这里在比较字符串"23"和"3"时返回true。因为两个操作数都是字符串,所以会逐个比较它们的字符编码(字符"2"的编码是50,而字符"3"的编码是51)。不过,如果有一个操作数是数值,那么比较的结果就对了:

let result = "23" < 3; // false

        因为这次会将字符串"23"转换为数值23,然后再跟3比较,结果当然对了。只要是数值和字符串比较,字符串就会先被转换为数值,然后进行数值比较。对于数值字符串而言,这样能保证结果正确。但如果字符串不能转换成数值呢?比如下面这个例子:

let result = "a" < 3; // 因为"a"会转换为NaN,所以结果是false

        因为字符"a"不能转换成任何有意义的数值,所以只能转换为NaN。这里有一个规则,即任何关系操作符在涉及比较NaN时都返回false。这样一来,下面的例子有趣了:

let result1 = NaN < 3; // false
let result2 = NaN >= 3; // false

        在大多数比较的场景中,如果一个值不小于另一个值,那就一定大于或等于它。但在比较NaN时,无论是小于还是大于等于,比较的结果都会返回false。

3.5.8 相等操作符

        判断两个变量是否相等是编程中最重要的操作之一。在比较字符串、数值和布尔值是否相等时,过程都很直观。但是在比较两个对象是否相等时,情形就比较复杂了。ECMAScript中的相等和不相等操作符,原本在比较之前会执行类型转换,但很快就有人质疑这种转换是否应该发生。最终,ECMAScript提供了两组操作符。第一组是等于和不等于,它们在比较之前执行转换。第二组是全等和不全等,它们在比较之前不执行转换。

1. 等于和不等于

        ECMAScript中的等于操作符用两个等于号(==)表示,如果操作数相等,则会返回true。不等于操作符用叹号和等于号(!=)表示,如果两个操作数不相等,则会返回true。这两个操作符都会先进行类型转换(通常称为强制类型转换)再确定操作数是否相等。
        在转换操作数的类型时,相等和不相等操作符遵循如下规则。

  • 如果任一操作数是布尔值,则将其转换为数值再比较是否相等。false转换为0,true转换为1。
  • 如果一个操作数是字符串,另一个操作数是数值,则尝试将字符串转换为数值,再比较是否相等。
  • 如果一个操作数是对象,另一个操作数不是,则调用对象的valueOf()方法取得其原始值,再根据前面的规则进行比较。

        在进行比较时,这两个操作符会遵循如下规则。

  • null和undefined相等。
  • null和undefined不能转换为其他类型的值再进行比较。
  • 如果有任一操作数是NaN,则相等操作符返回false,不相等操作符返回true。记住:即使两个操作数都是NaN,相等操作符也返回false,因为按照规则,NaN不等于NaN。
  • 如果两个操作数都是对象,则比较它们是不是同一个对象。如果两个操作数都指向同一个对象,则相等操作符返回true。否则,两者不相等。

        下表总结了一些特殊情况及比较的结果。

2.全等和不全等

        全等和不全等操作符与相等和不相等操作符类似,只不过它们在比较相等时不转换操作数。全等操作符由3个等于号(===)表示,只有两个操作数在不转换的前提下相等才返回true,比如:

let result1 = ("55" == 55); // true,转换后相等
let result2 = ("55" === 55); // false,不相等,因为数据类型不同

        在这个例子中,第一个比较使用相等操作符,比较的是字符串"55"和数值55。如前所述,因为字符串"55"会被转换为数值55,然后再与数值55进行比较,所以返回true。第二个比较使用全等操作符,因为没有转换,字符串和数值当然不能相等,所以返回false。
        不全等操作符用一个叹号和两个等于号(!==)表示,只有两个操作数在不转换的前提下不相等才返回true。比如:

let result1 = ("55" != 55); // false,转换后相等
let result2 = ("55" !== 55); // true,不相等,因为数据类型不同

        这一次,第一个比较使用不相等操作符,它会把字符串"55"转换为数值55,跟第二个操作数相等。既然转换后两个值相等,那就返回false。第二个比较使用不全等操作符。这时候可以这么问:“字符串55和数值55有区别吗?”答案是“有”(true)。
        另外,虽然null == undefined是true(因为这两个值类似),但null === undefined是false,因为它们不是相同的数据类型。

注意     由于相等和不相等操作符存在类型转换问题,因此推荐使用全等和不全等操作符。这样有助于在代码中保持数据类型的完整性。

3.5.9 条件操作符

        条件操作符是ECMAScript中用途最为广泛的操作符之一,语法跟Java中一样:

let variable = boolean_expression ? true_value : false_value;

        上面的代码执行了条件赋值操作,即根据条件表达式boolean_expression的值决定将哪个值赋给变量variable。如果boolean_expression是true,则赋值true_value;如果boolean_expression是false,则赋值false_value。比如:

let max = (num1 > num2) ? num1 : num2;

        在这个例子中,max将被赋予一个最大值。这个表达式的意思是,如果num1大于num2(条件表达式为true),则将num1赋给max。否则,将num2赋给max。

3.5.10 赋值操作符

        简单赋值用等于号(=)表示,将右手边的值赋给左手边的变量,如下所示:

let num = 10;

        复合赋值使用乘性、加性或位操作符后跟等于号(=)表示。这些赋值操作符是类似如下常见赋值操作的简写形式:

let num = 10;
num = num + 10;

        以上代码的第二行可以通过复合赋值来完成:

let 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
} 

        这里的条件(condition)可以是任何表达式,并且求值结果不一定是布尔值。ECMAScript会自动调用Boolean()函数将这个表达式的值转换为布尔值。如果条件求值为true,则执行语句statement1;如果条件求值为false,则执行语句statement2。这里的语句可能是一行代码,也可
能是一个代码块(即包含在一对花括号中的多行代码)。来看下面的例子:

if (i > 25)
    console.log("Greater than 25."); // 只有一行代码的语句
else {
    console.log("Less than or equal to 25."); // 一个语句块
}

        这里的最佳实践是使用语句块,即使只有一行代码要执行也是如此。这是因为语句块可以避免对什么条件下执行什么产生困惑。
        可以像这样连续使用多个if语句:

if (condition){
    statement1
}else if (condition2){
    statement2
}else{
    statement3
} 

        下面是一个例子:

if (i > 25) {
    console.log("Greater than 25.");
} else if (i < 0) {
    console.log("Less than 0.");
} else {
    console.log("Between 0 and 25, inclusive.");
}

 

3.6.2 do-while语句

        do-while语句是一种后测试循环语句,即循环体中的代码执行后才会对退出条件进行求值。换句话说,循环体内的代码至少执行一次。do-while的语法如下:

do {
statement
} while (expression);

        下面是一个例子:

let i = 0;
do {
    i += 2;
} while (i < 10);

        在这个例子中,只要i小于10,循环就会重复执行。i从0开始,每次循环递增2。

注意        后测试循环经常用于这种情况:循环体内代码在退出前至少要执行一次

3.6.3 while语句

        while语句是一种先测试循环语句,即先检测退出条件,再执行循环体内的代码。因此,while循环体内的代码有可能不会执行。下面是while循环的语法:

while(expression){
    statement
} 

        这是一个例子:

let i = 0;
while (i < 10) {
    i += 2;
}

        在这个例子中,变量i从0开始,每次循环递增2。只要i小于10,循环就会继续。

3.6.4 for语句

        for语句也是先测试语句,只不过增加了进入循环之前的初始化代码,以及循环执行后要执行的表达式,语法如下:

for (initialization; expression; post-loop-expression){
    statement
} 

        下面是一个用例:

let count = 10;
for (let i = 0; i < count; i++) {
    console.log(i);
}

        以上代码在循环开始前定义了变量i的初始值为0。然后求值条件表达式,如果求值结果为true(i < count),则执行循环体。因此循环体也可能不会被执行。如果循环体被执行了,则循环后表达式也会执行,以便递增变量i。for循环跟下面的while循环是一样的:

let count = 10;
let i = 0;
while (i < count) {
    console.log(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 (const propName in window) {
    document.write(propName);
}

        这个例子使用for-in循环显示了BOM对象window的所有属性。每次执行循环,都会给变量propName赋予一个window对象的属性作为值,直到window的所有属性都被枚举一遍。与for循环一样,这里控制语句中的const也不是必需的。但为了确保这个局部变量不被修改,推荐使用const。
        ECMAScript中对象的属性是无序的,因此for-in语句不能保证返回对象属性的顺序。换句话说,所有可枚举的属性都会返回一次,但返回的顺序可能会因浏览器而异。
        如果for-in循环要迭代的变量是null或undefined,则不执行循环体。

3.6.6 fort-of语句

        for-of语句是一种严格的迭代语句,用于遍历可迭代对象的元素,语法如下:

for (property of expression){
    statement
} 

        下面是示例:

for (const el of [2,4,6,8]) {
    document.write(el);
}

        在这个例子中,我们使用for-of语句显示了一个包含4个元素的数组中的所有元素。循环会一直持续到将所有元素都迭代完。与for循环一样,这里控制语句中的const也不是必需的。但为了确保这个局部变量不被修改,推荐使用const。
        for-of循环会按照可迭代对象的next()方法产生值的顺序迭代元素。如果尝试迭代的变量不支持迭代,则for-of语句会抛出错误。

注意         ES2018对for-of语句进行了扩展,增加了for-await-of循环,以支持生成期约(promise)的异步可迭代对象。

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语句为执行循环代码提供了更严格的控制手段。其中,break语句用于立即退出循环,强制执行循环后的下一条语句。而continue语句也用于立即退出循环,但会再次从循环顶部开始执行。
下面看一个例子:

let num = 0;
for (let i = 1; i < 10; i++) {
    if (i % 5 == 0) {
        break;
    }
    num++;
}
console.log(num); // 4

        在上面的代码中,for循环会将变量i由1递增到10。而在循环体内,有一个if语句用于检查i能否被5整除(使用取模操作符)。如果是,则执行break语句,退出循环。变量num的初始值为0,表示循环在退出前执行了多少次。当break语句执行后,下一行执行的代码是console.log(num),显示4。之所以循环执行了4次,是因为当i等于5时,break语句会导致循环退出,该次循环不会执行递增num的代码。如果将break换成continue,则会出现不同的效果:

let num = 0;
for (let i = 1; i < 10; i++) {
    if (i % 5 == 0) {
        continue;
    }
    num++;
}
console.log(num); // 8

        这一次,console.log显示8,即循环被完整执行了8次。当i等于5时,循环会在递增num之前退出,但会执行下一次迭代,此时i是6。然后,循环会一直执行到自然结束,即i等于10。最终num的值是8而不是9,是因为continue语句导致它少递增了一次。break和continue都可以与标签语句一起使用,返回代码中特定的位置。这通常是在嵌套循环中,如下面的例子所示:

let num = 0;
outermost:
for (let i = 0; i < 10; i++) {
    for (let j = 0; j < 10; j++) {
        if (i == 5 && j == 5) {
            break outermost;
        }
        num++;
    }
}
console.log(num); // 55

        在这个例子中,outermost标签标识的是第一个for语句。正常情况下,每个循环执行10次,意味着num++语句会执行100次,而循环结束时console.log的结果应该是100。但是,break语句带来了一个变数,即要退出到的标签。添加标签不仅让break退出(使用变量j的)内部循环,也会退出(使用变量i的)的外部循环。当执行到i和j都等于5时,循环停止执行,此时num的值是55。continue语句也可以使用标签,如下面的例子所示:

let num = 0;
outermost:
for (let i = 0; i < 10; i++) {
    for (let j = 0; j < 10; j++) {
        if (i == 5 && j == 5) {
            continue outermost;
        }
        num++;
    }
}
console.log(num); // 95

        这一次,continue语句会强制循环继续执行,但不是继续执行内部循环,而是继续执行外部循环。当i和j都等于5时,会执行continue,跳到外部循环继续执行,从而导致内部循环少执行5次,结果num等于95。组合使用标签语句和break、continue能实现复杂的逻辑,但也容易出错。注意标签要使用描述性强的文本,而嵌套也不要太深。

3.6.9 with语句

        with语句的用途是将代码作用域设置为特定的对象,其语法是:

with (expression){
    statement;
} 

        使用with语句的主要场景是针对一个对象反复操作,这时候将代码作用域设置为该对象能提供便利,如下面的例子所示:

let qs = location.search.substring(1);
let hostName = location.hostname;
let url = location.href;

        上面代码中的每一行都用到了location对象。如果使用with语句,就可以少写一些代码:

with(location) {
    let qs = search.substring(1);
    let hostName = hostname;
    let url = href;
}

        这里,with语句用于连接location对象。这意味着在这个语句内部,每个变量首先会被认为是一个局部变量。如果没有找到该局部变量,则会搜索location对象,看它是否有一个同名的属性。如果有,则该变量会被求值为location对象的属性。
        严格模式不允许使用with语句,否则会抛出错误。

警告   由于with语句影响性能且难于调试其中的代码,通常不推荐在产品代码中使用with语句。

3.6.10 switch语句

        switch语句是与if语句紧密相关的一种流控制语句,从其他语言借鉴而
来。ECMAScript中switch语句跟C语言中switch语句的语法非常相似,
如下所示:

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语句)。有了switch语句,开发者就用不着写类似这样的代码了:

if (i == 25) {
    console.log("25");
} else if (i == 35) {
    console.log("35");
} else if (i == 45) {
    console.log("45");
} else {
    console.log("Other");
}

        而是可以这样写:

switch (i) {
    case 25:
        console.log("25");
        break;
    case 35:
        console.log("35");
        break;
    case 45:
        console.log("45");
        break;
    default:
        console.log("Other");
}

        为避免不必要的条件判断,最好给每个条件后面都加上break语句。如果确实需要连续匹配几个条件,那么推荐写个注释表明是故意忽略了break,如下所示:

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语句可以用于所有数据类型(在很多语言中,它只能用于数值),因此可以使用字符串甚至对象。其次,条件的值不需要是常量,也可以是变量或表达式。看下面的例子:

switch ("hello world") {
    case "hello" + " world":
        console.log("Greeting was found.");
        break;
    case "goodbye":
        console.log("Closing was found.");
        break;
    default:
        console.log("Unexpected message was found.");
}

        这个例子在switch语句中使用了字符串。第一个条件实际上使用的是表达式,求值为两个字符串拼接后的结果。因为拼接后的结果等于switch的参数,所以console.log会输出"Greeting was found."。能够在条件判断中使用表达式,就可以在判断中加入更多逻辑:

let num = 25;
switch (true) {
    case num < 0:
        console.log("Less than 0.");
        break;
    case num >= 0 && num <= 10:
        console.log("Between 0 and 10.");
        break;
    case num > 10 && num <= 20:
        console.log("Between 10 and 20.");
        break;
    default:
        console.log("More than 20.");
}

        上面的代码首先在外部定义了变量num,而传给switch语句的参数之所以是true,就是因为每个条件的表达式都会返回布尔值。条件的表达式分别被求值,直到有表达式返回true;否则,就会一直跳到default语句(这个例子正是如此)。

注意         switch语句在比较每个条件的值时会使用全等操作符,因此不会强制转换数据类型(比如,字符串"10"不等于数值10)。

3.7 函数

        函数对任何语言来说都是核心组件,因为它们可以封装语句,然后在任何地方、任何时间执行。ECMAScript中的函数使用function关键字声明,后跟一组参数,然后是函数体。

        以下是函数的基本语法:

function functionName(arg0, arg1,...,argN) {
    statements
}

        下面是一个例子:

function sayHi(name, message) {
    console.log("Hello " + name + ", " + message);
}

        可以通过函数名来调用函数,要传给函数的参数放在括号里(如果有多个参数,则用逗号隔开)。下面是调用函数sayHi()的示例:

sayHi("Nicholas", "how are you today?");

        调用这个函数的输出结果是"Hello Nicholas, how are you today?"。参数name和message在函数内部作为字符串被拼接在了一起,最终通过console.log输出到控制台。
        ECMAScript中的函数不需要指定是否返回值。任何函数在任何时间都可以使用return语句来返回函数的值,用法是后跟要返回的值。比如:

function sum(num1, num2) {
    return num1 + num2;
}

        函数sum()会将两个值相加并返回结果。注意,除了return语句之外没有任何特殊声明表明该函数有返回值。然后就可以这样调用它:

const result = sum(5, 10);

         要注意的是,只要碰到return语句,函数就会立即停止执行并退出。因此,return语句后面的代码不会被执行。比如:

function sum(num1, num2) {
    return num1 + num2;
    console.log("Hello world"); // 不会执行
}

        在这个例子中,console.log不会执行,因为它在return语句后面。一个函数里也可以有多个return语句,像这样:

function diff(num1, num2) {
    if (num1 < num2) {
        return num2 - num1;
    } else {
        return num1 - num2;
    }
}

        这个diff()函数用于计算两个数值的差。如果第一个数值小于第二个,则用第二个减第一个;否则,就用第一个减第二个。代码中每个分支都有自己的return语句,返回正确的差值。
        return语句也可以不带返回值。这时候,函数会立即停止执行并返回undefined。这种用法最常用于提前终止函数执行,并不是为了返回值。比如在下面的例子中,console.log不会执行:

function sayHi(name, message) {
    return;
    console.log("Hello " + name + ", " + message); // 不会执行
}

注意         最佳实践是函数要么返回值,要么不返回值。只在某个条件下返回值的函数会带来麻烦,尤其是调试时。


        严格模式对函数也有一些限制:

  • 函数不能以eval或arguments作为名称;
  • 函数的参数不能叫eval或arguments;
  • 两个命名参数不能叫同一个名称。

        如果违反上述规则,则会导致语法错误,代码也不会执行。

3.8 小结

        JavaScript的核心语言特性在ECMA-262中以伪语言ECMAScript的形式来定义。ECMAScript包含所有基本语法、操作符、数据类型和对象,能完成基本的计算任务,但没有提供获得输入和产生输出的机制。理解ECMAScript及其复杂的细节是完全理解浏览器中JavaScript的关键。下面总结一下ECMAScript中的基本元素。

  • ECMAScript中的基本数据类型包括Undefined、Null、Boolean、Number、String和Symbol。
  • 与其他语言不同,ECMAScript不区分整数和浮点值,只有Number一种数值数据类型。
  • Object是一种复杂数据类型,它是这门语言中所有对象的基类。
  • 严格模式为这门语言中某些容易出错的部分施加了限制。
  • ECMAScript提供了C语言和类C语言中常见的很多基本操作符,包括数学操作符、布尔操作符、关系操作符、相等操作符和赋值操作符等。
  • 这门语言中的流控制语句大多是从其他语言中借鉴而来的,比如if语句、for语句和switch语句等。

        ECMAScript中的函数与其他语言中的函数不一样。

  • 不需要指定函数的返回值,因为任何函数可以在任何时候返回任何值。
  • 不指定返回值的函数实际上会返回特殊值undefined。
  • 64
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值