ECMA-262 描述了一组用于操作数据值的操作符,包括算术运算符、位操作符、关系操作符和相等操作符。ES 操作符的与众不同之处在于,它们能够适用于很多值,例如字符串、数值、布尔值,甚至对象。不过,在应用于对象时,相应的操作符通常都会调用对象的 valueOf()
和(或) toString()
方法,以便取得可以操作的值。
1.一元操作符
只能操作一个值的操作符叫做一元操作符。一元操作符是 ES 中最简单的操作符。
1.1 递增和递减操作符
递增和递减操作符直接借鉴自 C,而且各有两个版本:前置型和后置型。顾名思义,前置型应该位于要操作的变量之前,而后置型应该位于要操作的变量之后。因此,在使用前置递增操作符给一个数值加 1 时,要把两个加号放在这个数值变量之前,例如:
1 | var age = 29; |
在这个例子中,前置递增操作符把 age
的值变成了 30
(为 29 加上了 1)。实际上,执行这个前置递增操作与执行以下操作的效果相同:
1 | var age = 29; |
执行前置递减操作的方法也类似,结果会从一个数值中减去 1。使用前置递减操作符时,要把两个减号放在相应变量的前面。例如:
1 | var age = 29; |
这样,age
变量的值就减少为 28
。
执行前置递增和递减操作时,变量的值都是在语句被求值以前改变的。(在计算机科学领域,这种情况通常被称为副效应。)
1 | var age = 29; |
这个例子中变量 anotherAge
的初始值等于变量 age
的值前置递减之后加 2。由于先执行了减法操作,age
的值变成了 28,所以再加上 2 的结果就是 30。
由于前置递增和递减操作与执行语句的优先级相等,因此整个语句会从左至右被求值。再看一个例子:
1 | var num1 = 2; |
在这里,num3
之所以等于 21 是因为 num1
先减去了 1 才与 num2
相加。而变量 num4
也等于 21 是因为相应的加法操作使用了 num1
减去 1 之后的值。
后置型递增和递减操作符的语法不变,只不过要放在变量的后面而不是前面。后置递增和递减与前置递增和递减有一个非常重要的区别,即递增和递减操作是在包含它们的语句被求值之后才执行的。这个区别在某些情况下不是问题。例如:
1 | var age = 29; |
把递增操作符放在变量后面并不会改变语句的结果,因为递增是这条语句的唯一操作。但是,当语句中还包含其他操作时,上述区别就会非常明显了。例如:
1 | var num1 = 2; |
在前面的例子中,num3
和 num4
最后都等于 21。而在这个例子中,num3
等于 22,num4
等于 21。差别的根源在于,这里计算 num3
时使用了 num1
的原始值(2)完成了加法计算,而 num4
则使用了递减后的值(1)。
所有这 4 个操作符对任何值都使用,也就是它们不仅适用于整数,还可以用于字符串、布尔值、浮点数值和对象。在应用于不同的值时,递增和递减操作符遵循下列规则:
- 在应用于一个包含有效数字字符的字符串时,先将其转换为数字值,再执行加减 1 的操作。字符串变量变成数值变量。
- 在应用于一个不包含有效数字字符的字符串时,将变量的值设置为
NaN
。字符串变量变成数值变量。 - 在应用于布尔值
false
时,先将其转换为0
再执行加减 1 的操作。布尔值变量变成数值变量。 - 在应用于布尔值
true
时,先将其转换为1
再执行加减 1 的操作。布尔值变量变成了数值变量。 - 在应用于浮点数值时,执行加减 1 的操作。
- 在应用于对象时,先调用对象的
valueOf()
方法以取得一个可供操作的值。然后对该值应用前述规则。如果结果是NaN
,则在调用toString()
方法后再应用前述规则。对象变量变成数值变量。
以下示例展示了上面的一些规则:
1 | var s1 = "2"; |
1.2 一元加和减操作符
一元加操作符以一个加号表示,放在数值前面,对数值不会产生任何影响,例如:
1 | var num = 25; |
不过,在对非数值应用一元加操作符时,该操作符会像 Number()
转型函数一样对这个值执行转换。换句话说,布尔值 false
和 true
将被转换为 0 和 1,字符串值会被按照一组特殊的规则进行解析,而对象是先调用它们的 valueOf()
和(或) toString()
方法,再转换得到值。
下面的例子展示了对不同数据类型应用一元加操作符的结果:
1 | var s1 = "01"; |
一元减操作符主要用于表示负数,例如将 1 转换成 -1。
1 | var num = 25; |
在将一元减操作符应用于数值时,该值会变成负数。而当应用于非数值时,一元减操作符遵循与一元加操作符相同的规则,最后再将得到的数值转换为负数。例如:
1 | var s1 = "01"; |
一元加和减操作符主要用于基本的算术运算,也可以像前面示例所展示的一样用于转换数据类型。
2.位操作符
位操作符用于在最基本的层次上,即按内存中表示数值的位来操作数值。ES 中的所有数值都以 IEEE-764 64 位格式存储,但位操作符并不直接操作 64 位的值,而是先将 64 位的值转换为 32 位的整数,然后执行操作,最后再将结果转换回 64 位。对于开发人员而言,由于 64 位存储格式是透明的,因此整个过程就像是只存在 32 位的整数一样。
对于有符号的整数,32 位中的前 31 位用于表示整数的值。第 32 位用于表示数值的符号:0 表示正数,1 表示负数。这个表示符号的位叫做符号位,符号位的值决定了其他位数值的格式。其中,正数以纯二进制格式存储,31 位中的每一位都表示 2 的幂。第一位(叫做位 0)表示 2 的 0 次方,第二位表示 2 的 1 次方,以此类推。没有用到的位以 0 填充,即忽略不计。例如,数值 18 的二进制表示是 00000000000000000000000000010010
,或者更简洁的 10010
。这是 5 个有效位,这 5 位本身就决定了实际的值。如下图所示:
负数同样以二进制码存储,但使用的格式是二进制补码。计算一个数值的二进制补码,需要经过以下 3 个步骤:
- 求这个数值绝对值的二进制码(例如,要求 -18 的二进制补码,先求 18 的二进制码);
- 求二进制反码,即将 0 替换为 1,将 1 替换为 0;
- 得到的二进制反码加 1。
要根据这 3 个步骤求得 -18 的二进制码,首先就要求得 18 的二进制码,即:
1 | 0000 0000 0000 0000 0000 0000 0001 0010 |
然后,求其二进制反码,即 0 和 1 互换:
1 | 1111 1111 1111 1111 1111 1111 1110 1101 |
最后,二进制反码加 1:
1 | 1111 1111 1111 1111 1111 1111 1110 1101 |
这样,就求得了 -18 的二进制表示。要注意的是,在处理有符号整数时,是不能访问位 31 的。
ES 会尽力隐藏这些信息,在以二进制字符串形式输出一个负数时,看到的只是这个负数绝对值的二进制码前面加上了一个负号。例如:
1 | var num = -18; |
要把数值 -18 转换成二进制字符串时,得到的结果是 "-10010"
。这说明转换过程理解了二进制补码并将其以更合乎逻辑的形式展示了出来。
默认情况下,ES 中的所有整数都是有符号整数。不过,当然也存在无符号整数。对于无符号整数来说,第 32 位不再表示符号,因为无符号整数只能是正数。而且,无符号整数的值可以更大,因为多出的一位不再表示符号,可以用来表示数值。
在 ES 中,当对数值应用位操作符时,后台会发生如下转换过程:64 位的数值转换成 32 位数值,然后执行位操作,最后再将 32 位的结果转换回 64 位数值。这样,表面上看起来好像是在操作 32 位数值。但这个转换过程也导致了一个严重的副效应,即在对特殊的 NaN
和 Infinity
值应用位操作时,这两个值都会被当成 0 来处理。
如果对非数值应用位操作符,会先使用 Number()
函数将该值转换为一个数值(自动完成),然后再应用位操作。得到的结果是一个数值。
2.1 按位非(NOT)
按位非操作符由一个波浪线(~
)表示,执行按位非的结果就是返回数值的反码。按位非是 ES 操作符中少数几个与二进制计算有关的操作符之一。
1 | var num1 = 25; // 二进制 00000000000000000000000000011001 |
这里,对 25 执行按位非操作,结果得到了 -26。这也验证了按位非操作的本质:操作数的负值减 1。因此,下面的代码也能得到相同的结果:
1 | var num1 = 25; |
虽然以上代码也能返回同样的结果,但由于按位非是在数值表示的最底层执行操作,因此速度更快。
2.2 按位与(AND)
按位与操作符由一个和号字符(&
)表示,它有两个操作数。从本质上讲,按位与操作就是将两个数值的每一位对齐,然后根据下表中的规则,对相同位置上的两个数执行 AND 操作:
第一个数值的位 | 第二个数值的位 | 结果 |
---|---|---|
1 | 1 | 1 |
1 | 0 | 0 |
0 | 1 | 0 |
0 | 0 | 0 |
简而言之,按位与操作只有在两个数值的对应位都是 1 时才返回 1,任何一位是 0,结果都是 0。
1 | var result = 25 & 3; |
上例可见,对 25 和 3 执行按位与操作的结果是 1。底层操作如下:
1 | 25 = 0000 0000 0000 0000 0000 0000 0001 1001 |
可见,25 和 3 的二进制码对应位上只有一位同时是 1,而其他位的结果自然都是 0,因此最终结果等于 1。
2.3 按位或(OR)
按位或操作符由一个竖线符号(|
)表示,同样也有两个操作数。按位或操作遵循下面这个真值表:
第一个数值的位 | 第二个数值的位 | 结果 |
---|---|---|
1 | 1 | 1 |
1 | 0 | 1 |
0 | 1 | 1 |
0 | 0 | 0 |
由此可见,按位或操作在有一个位是 1 的情况下就返回 1,而只有在两个位都是 0 的情况下才返回 0。
如果在前面按位与的例子中对 25 和 3 执行按位或操作,则代码如下所示:
1 | var result = 25 | 3; |
25 与 3 按位或的结果是 27:
1 | 25 = 0000 0000 0000 0000 0000 0000 0001 1001 |
这两个数值都包含 4 个 1,因此可以把每个 1 直接放到结果中。二进制码 11011 等于十进制 27。
2.4 按位异或(XOR)
按位异或操作符由一个插入符号(^
)表示,也有两个操作数。以下是按位异或的真值表:
第一个数值的位 | 第二个数值的位 | 结果 |
---|---|---|
1 | 1 | 0 |
1 | 0 | 1 |
0 | 1 | 1 |
0 | 0 | 0 |
按位异或与按位或的不同之处在于,这个操作在两个数值对应位上只有一个 1 时才返回 1,如果对应的两位都是 1 或都是 0,则返回 0。
对 25 和 3 执行按位异或操作的代码如下所示:
1 | var result = 25 ^ 3; |
25 与 3 按位异或的结果是 26,底层操作如下所示:
1 | 25 = 0000 0000 0000 0000 0000 0000 0001 1001 |
这两个数值都包含 4 个 1,但第一位上则都是 1,因此结果的第一位变成了 0;而其他位上的 1 在另一个数值中都没有对应的 1,可以直接放到结果中。二进制码 11010 等于十进制的 26。
2.5 左移
左移操作符由两个小于号(<<
)表示,这个操作符会将数值的所有位向左移动指定的位数。例如,如果将数值 2(二进制码位 10)向左移动 5 位,结果就是 64(二进制码位 1000000),代码如下所示:
1 | var oldValue = 2; // 二进制 10 |
注意,向左移位后,原数值的右侧多出 5 个空位。左移操作会以 0 来填充这些空位,以便得到的结果是一个完整的 32 位二进制数。
左移操作不会影响操作数的符号位。换句话说,如果将 -2 向左移动 5 位,结果将是 -64,而非 64。
2.6 有符号的右移
有符号的右移操作符由两个大于号(>>
)表示,这个操作符会将数值向右移动,但保留符号位(即正负号标记)。有符号的右移操作与左移操作恰好相反,即如果将 64 向右移动 5 位,结果将变回 2:
1 | var oldValue = 64; // 二进制的 1000000 |
同样,在移位过程中,原数值也会出现空位,只不过这次的空位出现在原数值的左侧、符号位的右侧。而此时 ES 会用符号位的值来填充所有空位,以便得到一个完整的值。
2.7 无符号右移
无符号右移操作符由 3 个大于号(>>>
)表示,这个操作符会将数值的所有 32 位都向右移动。对正数来说,无符号右移的结果与有符号右移相同。但对负数来说,情况就不一样了。
首先,无符号右移是以 0 来填充空位,而不是像有符号右移那样以符号位的值来填充空位。所以,对正数的无符号右移与有符号右移结果相同,但对负数的结果就不一样了。其次,无符号右移操作符会把负数的二进制码当成正数的二进制码。而且,由于负数以其绝对值的二进制补码形式表示,因此就会导致无符号右移后的结果非常大。如下面的例子所示:
1 | var oldValue = -64; // 二进制的 11111111111111111111111111000000 |
这里,当对 -64 执行无符号右移 5 位的操作后,得到的结果是 134217726。之所以结果如此之大,是因为 -64 的二进制码为 11111111111111111111111111000000,而且无符号右移操作会把这个二进制码当成正数的二进制码,换算成十进制就是 4294967232。如果把这个值右移 5 为,结果就变成了 00000111111111111111111111111110,即十进制的 134217726。
3.布尔操作符
在一门编程语言中,布尔操作符的重要性堪比相等操作符。如果没有测试两个值关系的能力,那么诸如 if...else
和循环之类的语句就不会有用武之地了。布尔操作符一共有 3 个:非(NOT)、与(AND)和或(OR)。
3.1 逻辑非
逻辑非操作符由一个叹号(!
)表示,可以应用于 ES 中的任何值。无论这个值是什么数据类型,这个操作符都会返回一个布尔值(不能对未声明的变量使用)。逻辑非操作符首先会将它的操作数转换为一个布尔值,然后再对其求反。也就是说,逻辑非操作符遵循下列规则:
- 如果操作数是一个对象,返回
false
; - 如果操作数是一个空字符串,返回
true
; - 如果操作数是一个非空字符串,返回
false
; - 如果操作数是数值 0,返回
true
; - 如果操作数是任意非 0 数值(包括 Infinity),返回
false
; - 如果操作数是 null,返回
true
; - 如果操作数是 NaN,返回
true
; - 如果操作数是 undefined,返回
true
;
下面几个例子展示了应用上述规则的结果:
1 | alert(!false); // true |
逻辑非操作符也可以用于将一个值转换为与其对应的布尔值。而同时使用两个逻辑非操作符,实际上就会模拟 Boolean()
转型函数的行为。其中,第一个逻辑非操作会基于无论什么操作数返回一个布尔值,而第二个逻辑非操作则对该布尔值求反,于是就得到了这个值真正对应的布尔值。当然,最终结果与对这个值使用 Boolean()
函数相同,如下面的例子所示:
1 | alert(!!"blue"); // true |
3.2 逻辑与
逻辑与操作符由两个和号(&&
)表示,有两个操作数,如下面的例子所示:
1 | var result = true && false; |
逻辑与的真值表如下:
第一个操作数 | 第二个操作数 | 结果 |
---|---|---|
true | true | true |
true | false | false |
false | true | false |
false | false | false |
逻辑与操作可以应用于任何类型的操作数,而不仅仅是布尔值。在有一个操作数不是布尔值的情况下,逻辑与操作就不一定返回布尔值;此时,它遵循下列规则:
- 如果第一个操作数是对象,则返回第二个操作数;
- 如果第二个操作数是对象,则只有在第一个操作数的求值结果为
true
的情况下才会返回该对象; - 如果两个操作数都是对象,则返回第二个操作数;
- 如果有一个操作数是
null
, 则返回null
; - 如果有一个操作数是
NaN
, 则返回NaN
; - 如果有一个操作数是
undefined
,则返回undefined
。
逻辑与操作属于短路操作,即如果第一个操作数能够决定结果,那么就不会再对第二个操作数求值。对于逻辑与操作而言,如果第一个操作数是 false
,则无论第二个操作数是什么值,结果都不再可能是 true
了。
1 | var found = true; |
在上面的代码中,当执行逻辑与操作室会发生错误,因为变量 someUndefinedVariable
没有声明。由于变量 found
的值是 true
,所以逻辑与操作符会继续对变量 someUndefinedVariable
求值,因此就会导致错误。这说明不能再逻辑与操作中使用未定义的值。如果像下面这个例子中一样,将 found
的值设置为 false
,就不会发生错误了:
1 | var found = false; |
在这个例子中,警告框会显示出来。无论变量 someUndefinedVariable
有没有定义,也永远不会对它求值。
3.3 逻辑或
逻辑或操作符由两个竖线符号(||
)表示,有两个操作数,如下面的例子所示:
1 | var result = true || false; |
逻辑或的真值表如下:
第一个操作数 | 第二个操作数 | 结果 |
---|---|---|
true | true | true |
true | false | true |
false | true | true |
false | false | false |
与逻辑与操作相似,如果有一个操作数不是布尔值,逻辑或也不一定返回布尔值;此时,它遵循下列规则:
- 如果第一个操作数是对象,则返回第一个操作数;
- 如果第一个操作数的求值结果为
false
,则返回第二个操作数; - 如果两个操作数都是对象,则返回第一个操作数;
- 如果两个操作数都是
null
,则返回null
; - 如果两个操作数都是
NaN
,则返回NaN
; - 如果两个操作数都是
undefined
,则返回undefined
;
与逻辑与操作符相似,逻辑或操作符也是短路操作符。也就是说,如果第一个操作数的求值结果为 true
,就不会对第二个操作数求值了。下面看一个例子:
1 | var found = true; |
这个例子跟前面的例子一样,变量 someUndefinedVariable
也没有定义。但是,由于变量 found
的值是 true
,而变量 someUndefinedVariable
永远不会被求值,因此结果就会输出 "true"
。如果像下面这个例子一样,把 found
的值改为 false
,就会导致错误:
1 | var found = false; |
我们可以利用逻辑或的这一行为来避免为变量赋 null
或 undefined
值。例如:
1 | var myObject = preferredObject || backupObject; |
在这个例子中,变量 myObject
将被赋予等号后面两个值中的一个。变量 preferredObject
中包含优先赋给变量 myObject
的值,变量 backupObject
负责在 preferredObject
中不包含有效值的情况下提供后备值。如果 preferredObject
的值不是 null
,那么它的值将被赋给 myObject
;反之,则将 backupObject
的值赋给 myObject
。ES 程序的赋值语句经常会使用这种模式。
4.乘性操作符
ES 定义了 3 个乘性操作符:乘法、除法和求模 。这些操作符与 Java、C 或者 Per1 中的相应操作符用途类似,只不过在操作数为非数值的情况下会执行自动的类型转换。如果参与乘性计算的某个操作数不是数值,后台会先使用 Number()
转型函数将其转换为数值。也就是说,空字符串将被当作 0 ,布尔值 true 将被当作 1 。
4.1 乘法
乘法操作符由一个星号(*
)表示,用于计算两个数值的乘积。其语法类似于 C,如下面的例子所示:
1 | var result = 34 * 56; |
在处理特殊值的情况下,乘法操作符遵循下列特殊的规则:
- 如果操作数都是数值,执行常规的乘法计算,即两个正数或两个负数相乘的结果还是正数,而如果只有一个操作数有符号,那么结果就是负数。如果乘积超过了 ES 数值的表示范围,则返回
Infinity
或-Infinity
。 - 如果有一个操作数是
NaN
,则结果是NaN
; - 如果是
Infinity
与0
相乘,则结果是NaN
; - 如果是
Infinity
与非0
数值相乘,则结果是Infinity
或-Infinity
; - 如果是
Infinity
与Infinity
相乘,则结果是Infinity
; - 如果有一个操作数不是数值,则在后台调用
Number()
将其转换为数值,然后再应用上面的规则。
4.2 除法
除法操作符由一个斜线符号(/
)表示,执行第二个操作数除第一个操作数的计算,如下面的例子所示:
1 | var result = 66 / 11; |
与乘法操作符类似,除法操作苏对特殊的值也有特殊的处理规则。
- 如果操作数都是数值,执行常规的除法计算,即两个正数或两个负数相除的结果还是正数,而如果只有一个操作数有符号,那么结果就是负数。如果商超过了 ES 数值的表示范围,则返回
Infinity
或-Infinity
; - 如果有一个操作数是
NaN
,则结果是NaN
; - 如果是
Infinity
被Infinity
除,则结果是NaN
; - 如果是零被零除,则结果是
NaN
; - 如果是非零的有限数被零除,则结果是
Infinity
或-Infinity
,取决于有符号操作数的符号; - 如果是
Infinity
被任何非零值除,则结果是Infinity
或-Infinity
,取决于有符号操作数的符号; - 如果有一个操作数不是数值,则在后台调用
Number()
将其转换为数值,然后再应用上面的规则。
4.3 求模
求模(余数)操作数由一个百分号(&
)表示,用法如下:
1 | var result = 26 % 5; // 等于 1 |
与另外两个乘性操作符类似,求模操作符会遵循下列特殊规则的来处理特殊的值:
- 如果操作数都是数值,执行常规的除法计算,返回除得的余数;
- 如果被除数是无穷大值而除数是有限大的数值,则结果是
NaN
; - 如果被除数是有限大的数值而除数是零,则结果是
NaN
; - 如果是
Infinity
被Infinity
除,则结果是NaN
; - 如果被除数是有限大的数值而除数是无穷大的数值,则结果是被除数;
- 如果被除数是零,则结果是零;
- 如果有一个操作数不是数值,则再后台调用
Number()
将其转换为数值,然后再应用上面的规则。
5.加性操作符
加法和减法这两个加性操作符应该说是编程语言中最简单的算术操作符了。但是 ES 中,这两个操作符却都有一系列的特殊行为。与乘性操作符类似,加性操作符也会在后台转换不同的数据类型。然而,对于加性操作符而言,相应的转换规则还稍微有点复杂。
5.1 加法
加法操作符的用法如下所示:
1 | var 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"
。
下面来举几个例子:
1 | var result1 = 5 + 5; // 两个数值相加 |
以上代码演示了加法操作符在两种模式下的差别。第一行代码演示了正常的情况,即 5+5
等于 10
(数值)。但是,如果将一个操作数改为字符串 "5"
,结果就变成了 "55"
(字符串值),因为第一个操作数也被转换成了 "5"
。
忽视加法操作中的数据类型是 ES 编程中最常见的一个错误。例如:
1 | var num1 = 5; |
在这个例子中,变量 message
的值是执行两个加法操作之后的结果。第一个加法操作将一个字符串和一个数值(5
)拼接了起来,结果是一个字符串。而第二个加法操作又用这个字符串去加另一个数值(10
),当然也会得到一个字符串。如果想先对数值执行算术计算,然后再将结果与字符串拼接起来,应该像下面这样使用圆括号:
1 | var num1 = 5; |
在这个例子中,一对圆括号把两个数值变量括在了一起,这样就会告诉解析器先计算其结果,然后再将结果与字符串拼接起来。因此,就得到了结果 "The sum of 5 and 10 is 15"
。
5.2 减法
减法操作符是另一个极为常用的操作符,其用法如下所示:
1 | var result = 2 - 1; |
与加法操作符类似,ES 中的减法操作符在处理各种数据类型转换时,同样需要遵循一些特殊规则,如下所示:
- 如果两个操作符都是数值,则执行常规的算术减法操作并返回结果;
- 如果有一个操作数是
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()
方法并将得到的字符串转换为数值。
下面几个例子展示了上面的规则:
1 | var result1 = 5 - true; // 4,因为 true 被转换成了 1 |