《JavaScript 闯关记》之表达式和运算符
表达式
表达式是由数字、运算符、数字分组符号(如括号)、自由变量和约束变量等以能求得数值的有意义排列方法所得的组合。JavaScript 表达式主要有以下几种形式:
- 原始表达式:常量、变量、保留字。
- 对象、数组初始化表达式:
var obj={a:1,b:2};
,var arr=[1,2,3];
。 - 函数定义表达式:
var fn=function(){}
。 - 属性访问表达式:
Math.abs
。 - 调用表达式:
alert('hello');
。 - 对象创建表达式:
new object();
。
运算符
JavaScript 中的运算符用于算术表达式、比较表达式、逻辑表达式、赋值表达式等。需要注意的是,大多数运算符都是由标点符号表示的,比如 +
和 =
。而另外一些运算符则是由关键字表示的,比如 typeof
和 instanceof
,关键字运算符和标点符号都是正规的运算符。
下表列出了 JavaScript 中所有的运算符,并按照运算符的优先级排序的,前面的运算符优先级要高于后面的运算符优先级,被空行分隔开来的运算符具有不同的优先级。标题为 A 的列表示运算符的结合性(Associativity),L 表示从左至右、R 表示从右至左,标题为 N 的列表示操作数的个数(Number)。
运算符 | 操作 | A | N |
---|---|---|---|
++ |
前/后增量 | R | 1 |
-- |
前/后增量 | R | 1 |
- |
求反 | R | 1 |
+ |
转换为数字 | R | 1 |
~ |
按位求反 | R | 1 |
! |
逻辑非 | R | 1 |
delete |
删除属性 | R | 1 |
typeof |
检测类型 | R | 1 |
void |
返回undefined |
R | 1 |
* / % |
乘,除,求模 | L | 2 |
+ - |
加,减 | L | 2 |
+ |
字符串连接 | L | 2 |
<< |
左移位 | L | 2 |
>> |
有符号右移 | L | 2 |
>>> |
无符号右移 | L | 2 |
< <= > >= |
比较数字顺序 | L | 2 |
< <= > >= |
比较字母顺序 | L | 2 |
instanceof |
测试对象类 | L | 2 |
in |
测试属性是否存在 | L | 2 |
== |
判断相等 | L | 2 |
!= |
判断不等 | L | 2 |
=== |
判断恒等 | L | 2 |
!== |
判断恒不等 | L | 2 |
& |
按位与 | L | 2 |
^ |
按位异或 | L | 2 |
┃ |
按位或 | L | 2 |
&& |
逻辑与 | L | 2 |
┃┃ |
逻辑或 | L | 2 |
?: |
条件运算符 | R | 3 |
= |
赋值 | R | 2 |
*= /= %= += -= &= <<= >>= ^= ┃= >>>= |
运算且赋值 | R | 2 |
, |
忽略第一个操作数, 返回第二个操作数 |
L | 2 |
因为
|
是制表符,会导致格式混乱,所以表格中的|
均以┃
代替。
一元运算符
delete
运算符
delete
运算符用来删除对象属性或者数组元素,如果删除成功或所删除的目标不存在,delete
将返回 true
。然而,并不是所有的属性都可删除,一些内置核心和客户端属性是不能删除的,通过 var
语句声明的变量不能删除,通过 function
语句定义的函数也是不能删除的。例如:
|
|
void
运算符
void
运算符可以应用于任何表类型的表达式,表达式会被执行,但计算结果会被忽略并返回 undefined
。例如:
|
|
扩展阅读「谈谈 JavaScript 中的 void 运算符」
https://segmentfault.com/a/1190000000474941
typeof
运算符
++
--
运算符
++
--
递增递减运算符借鉴自 C 语言,它们分前置型和后置型,作用是改变一个变量的值。例如:
|
|
+
-
运算符
当 +
-
作为一元运算符时,应用于数值,表示数值的正负。应用于非数值,先按 Number()
转型函数对这个值执行转换,再表示该值的正负。
~
!
运算符
乘性运算符
JavaScript 定义了3个乘性运算符:乘法、除法和求模。这些运算符与 C 语言的相应运算符用途类似,只不过在操作数为非数值的情况下会执行自动的类型转换。如果参与乘法计算的某个操作数不是数值,后台会先使用 Number()
转型函数将其转换为数值。也就是说,空字符串将被当作 0
,布尔值 true
将被当作 1
。
*
乘法运算符
用于计算两个数值的乘积,在处理特殊值的情况下,乘法运算符遵循下列特殊的规则:
- 如果操作数都是数值,执行常规的乘法计算,即两个正数或两个负数相乘的结果还是正数,而如果只有一个操作数有符号,那么结果就是负数。如果乘积超过了 JavaScript 数值的表示范围,则返回
Infinity
或-Infinity
; - 如果有一个操作数是
NaN
,则结果是NaN
; - 如果是
Infinity
与0
相乘,则结果是NaN
; - 如果是
Infinity
与非0
数值相乘,则结果是Infinity
或-Infinity
,取决于有符号操作数的符号; - 如果是
Infinity
与Infinity
相乘,则结果是Infinity
;
如果有一个操作数不是数值,则在后台调用Number()
将其转换为数值,然后再应用上面的规则。
/
除法运算符
用于计算两个数值的商,与乘法运算符类似,除法运算符对特殊的值也有特殊的处理规则。这些规则如下:
- 如果操作数都是数值,执行常规的除法计算,即两个正数或两个负数相除的结果还是正数,而如果只有一个操作数有符号,那么结果就是负数。如果商超过了 JavaScript 数值的表示范围,则返回
Infinity
或-Infinity
; - 如果有一个操作数是
NaN
,则结果是NaN
; - 如果是
Infinity
被Infinity
除,则结果是NaN
; - 如果是零被零除,则结果是
NaN
; - 如果是非零的有限数被零除,则结果是
Infinity
或-Infinity
,取决于有符号操作数的符号; - 如果是
Infinity
被任何非零数值除,则结果是Infinity
或-Infinity
,取决于有符号操作数的符号; - 如果有一个操作数不是数值,则在后台调用
Number()
将其转换为数值,然后再应用上面的规则。
%
求模运算符
用于计算两个数值的余数,与另外两个乘性运算符类似,求模运算符会遵循下列特殊规则来处理特殊的值:
- 如果操作数都是数值,执行常规的除法计算,返回除得的余数;
- 如果被除数是无穷大值而除数是有限大的数值,则结果是
NaN
; - 如果被除数是有限大的数值而除数是零,则结果是
NaN
; - 如果是
Infinity
被Infinity
除,则结果是NaN
; - 如果被除数是有限大的数值而除数是无穷大的数值,则结果是被除数;
- 如果被除数是零,则结果是零;
- 如果有一个操作数不是数值,则在后台调用
Number()
将其转换为数值,然后再应用上面的规则。
加性运算符
加法和减法这两个加性运算符应该说是编程语言中最简单的算术运算符了。但是在 JavaScript 中,这两个运算符却都有一系列的特殊行为。与乘性运算符类似,加性运算符也会在后台转换不同的数据类型。然而,对于加性运算符而言,相应的转换规则还稍微有点复杂。
+
加法运算符
如果两个运算符都是数值,执行常规的加法计算,然后根据下列规则返回结果:
- 如果有一个操作数是
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"
。 - 如果是
null
加null
,则结果是0
; - 如果是
undefined
加undefined
,则结果是NaN
;
下面来举几个例子:
|
|
-
减法运算符
如果两个运算符都是数值,执行常规的减法计算,然后根据下列规则返回结果:
- 如果有一个操作数是
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()
方法并将得到的字符串转换为数值。 - 如果是
null
减null
,则结果是0
; - 如果是
undefined
减undefined
,则结果是NaN
;
下面来举几个例子:
|
|
等值运算符
确定两个变量是否相等是编程中的一个非常重要的操作。在比较简单数据类型之间的相等性时,问题还比较简单。但在涉及到对象之间的比较时,问题就变得复杂了。最早的 JavaScript 中的相等和不等运算符会在执行比较之前,先将对象转换成相似的类型。后来,有人提出了这种转换到底是否合理的质疑。最后,JavaScript 的解决方案就是提供两组运算符:相等和不相等(先转换再比较),恒等和不恒等(仅比较而不转换)。
==
!=
运算符
==
!=
这两个运算符都会先转换操作数(通常称为强制转型),然后再比较它们的相等性。在转换不同的数据类型时,相等和不相等运算符遵循下列基本规则:
- 如果有一个操作数是布尔值,则在比较相等性之前先将其转换为数值(
false
转换为0
,而true
转换为1
); - 如果一个操作数是字符串,另一个操作数是数值,在比较相等性之前先将字符串转换为数值;
- 如果一个操作数是对象,另一个操作数不是,则调用对象的
valueOf()
方法,用得到的基本类型值按照前面的规则进行比较; null
和undefined
是相等的。
要比较相等性之前,不能将null
和undefined
转换成其他任何值。- 如果有一个操作数是
NaN
,则相等运算符返回false
,而不相等运算符返回true
。重要提示:即使两个操作数都是NaN
,相等运算符也返回false
;因为按照规则,NaN
不等于NaN
。 - 如果两个操作数都是对象,则比较它们是不是同一个对象。如果两个操作数都指向同一个对象,则相等运算符返回
true
;否则,返回false
。
列出了一些特殊情况及比较结果:
|
|
===
!==
运算符
除了在比较之前不转换操作数之外,恒等和不恒等运算符与相等和不相等运算符没有什么区别。它只在两个操作数未经转换就相等的情况下返回 true
,如下面的例子所示:
|
|
关系运算符
<
>
<=
>=
运算符
<
小于、>
大于、<=
小于等于、 >=
大于等于 这几个关系运算符用于对两个值进行比较返回一个布尔值。与 JavaScript 中的其他运算符一样,当关系运算符的操作数使用了非数值时,也要进行数据转换或完成某些奇怪的操作。以下就是相应的规则。
- 如果两个操作数都是数值,则执行数值比较。
- 如果两个操作数都是字符串,则比较两个字符串对应的字符编码值(可以通过字符串的
charCodeAt()
函数获取字符编码值)。 - 如果一个操作数是数值,则将另一个操作数转换为一个数值,然后执行数值比较。
- 如果一个操作数是对象,则调用这个对象的
valueOf()
方法,用得到的结果按照前面的规则执行比较。如果对象没有valueOf()
方法,则调用toString()
方法,并用得到的结果根据前面的规则执行比较。 - 如果一个操作数是布尔值,则先将其转换为数值,然后再执行比较。
请思考下面几个例子的结果是如何得出的:
|
|
in
运算符
in
运算符希望它的左操作数是一个字符串或可以转换为字符串,希望它的右操作数是一个对象。如果右侧的对象拥有一个名为左操作数值的属性名,那么表达式返回 true
,例如:
|
|
instanceof
运算符
instanceof
运算符希望左操作数是一个对象,右操作数标识对象的类。如果左侧的对象是右侧类的实例,则表达式返回 true
;否则返回 false
。后面会讲 JavaScript 中对象的类是通过初始化它们的构造函数来定义的。这样的话,instanceof
的右操作数应当是一个函数。比如:
|
|
需要注意的是,所有的对象都是 Object
的实例。当通过 instanceof
判断一个对象是否是一个类的实例的时候,这个判断也会包含对「父类」的检测。如果 instanceof
的左操作数不是对象的话,instanceof
返回 false
。如果右操作数不是函数,则抛出一个类型错误异常。
逻辑运算符
逻辑运算符是对操作数进行布尔算术运算,经常和关系运算符一起配合使用,逻辑运算符将多个关系表达式组合起来组成一个更复杂的表达式。
&&
逻辑与
逻辑与操作可以应用于任何类型的操作数,而不仅仅是布尔值。在有一个操作数不是布尔值的情况下,逻辑与操作不一定返回布尔值;此时,它遵循下列规则:
- 如果第一个操作数是对象,则返回第二个操作数;
- 如果第二个操作数是对象,则只有在第一个操作数的求值结果为
true
的情况下才会返回该对象; - 如果两个操作数都是对象,则返回第二个操作数;
- 如果有一个操作数是
null
,则返回null
; - 如果有一个操作数是
NaN
,则返回NaN
; - 如果有一个操作数是
undefined
,则返回undefined
。
逻辑与操作属于短路操作,即如果第一个操作数能够决定结果,那么就不会再对第二个操作数求值。对于逻辑与操作而言,如果第一个操作数是 false
,无论第二个操作数是什么值,结果都不再可能是 true
了。
||
逻辑或
与逻辑与操作相似,如果有一个操作数不是布尔值,逻辑或也不一定返回布尔值;此时,它遵循下列规则:
- 如果第一个操作数是对象,则返回第一个操作数;
- 如果第一个操作数的求值结果为
false
,则返回第二个操作数; - 如果两个操作数都是对象,则返回第一个操作数;
- 如果两个操作数都是
null
,则返回null
; - 如果两个操作数都是
NaN
,则返回NaN
; - 如果两个操作数都是
undefined
,则返回undefined
。
与逻辑与运算符相似,逻辑或运算符也是短路运算符。也就是说,如果第一个操作数的求值结果为 true
,就不会对第二个操作数求值了。
!
逻辑非
逻辑非操作可以应用于任何类型的操作数,无论这个值是什么数据类型,这个运算符都会返回一个布尔值。逻辑非运算符首先会将它的操作数转换为一个布尔值,然后再对其求反。逻辑非运算符遵循下列规则:
- 如果操作数是一个对象,返回
false
; - 如果操作数是一个空字符串,返回
true
; - 如果操作数是一个非空字符串,返回
false
; - 如果操作数是数值
0
,返回true
; - 如果操作数是任意非
0
数值(包括Infinity
),返回false
; - 如果操作数是
null
,返回true
; - 如果操作数是
NaN
,返回true
; - 如果操作数是
undefined
,返回true
。
下面几个例子展示了应用上述规则的结果:
|
|
逻辑非运算符也可以用于将一个值转换为与其对应的布尔值。而同时使用两个逻辑非运算符,实际上就会模拟 Boolean()
转型函数的行为。其中,第一个逻辑非操作会基于无论什么操作数返回一个布尔值,而第二个逻辑非操作则对该布尔值求反,于是就得到了这个值真正对应的布尔值。当然,最终结果与对这个值使用 Boolean()
函数相同,例如:
|
|
位运算符
在 JavaScript 中,当对数值应用位运算符时,后台会发生如下转换过程:64位的数值被转换成32位数值,然后执行位操作,最后再将32位的结果转换回64位数值。这个转换过程导致了一个严重的副效应,即在对特殊的 NaN
和 Infinity
值应用位操作时,这两个值都会被当成 0
来处理。如果对非数值应用位运算符,会先使用 Number()
函数将该值转换为一个数值,然后再应用位操作,得到的结果将是一个数值。
~
按位非
简单的理解,对任一数值 x
进行按位非操作的结果为 -(x+1)
。例如:
|
|
&
按位与
按位与操作就是将两个数值的每一位对齐,两个数值的对应位都是 1
时才返回 1
,任何一位是 0
,结果都是 0
。如下表所示:
第一个数值的位 | 第二个数值的位 | 结果 |
---|---|---|
1 | 1 | 1 |
1 | 0 | 0 |
0 | 1 | 0 |
0 | 0 | 0 |
|
按位或
按位或操作就是将两个数值的每一位对齐,两个数值只要有一个位是 1
就返回 1
,只在两个位都是 0
的情况下才返回 0
。如下表所示:
第一个数值的位 | 第二个数值的位 | 结果 |
---|---|---|
1 | 1 | 1 |
1 | 0 | 1 |
0 | 1 | 1 |
0 | 0 | 0 |
^
按位异或
按位异或与按位或的不同之处在于,两个数值只有一个 1
时才返回 1
,如果对应的两位都是 1
或都是 0
,则返回 0
。
第一个数值的位 | 第二个数值的位 | 结果 |
---|---|---|
1 | 1 | 0 |
1 | 0 | 1 |
0 | 1 | 1 |
0 | 0 | 0 |
<<
左移
这个运算符会将数值的所有位向左移动指定的位数。例如:
|
|
注意,左移不会影响操作数的符号位。换句话说,如果将 -2
向左移动 5
位,结果将是 -64
,而非 64
。
>>
有符号的右移
这个运算符会将数值向右移动,但保留符号位(即正负号标记)。
|
|
>>>
无符号的右移
这个运算符会将数值的所有32位都向右移动。对正数来说,无符号右移的结果与有符号右移相同。
|
|
无符号右移运算符会把负数的二进制码当成正数的二进制码。而且,由于负数以其绝对值的二进制补码形式表示,因此就会导致无符号右移后的结果非常之大。
|
|
赋值运算符
简单的赋值运算符由等于号 =
表示,其作用就是把右侧的值赋给左侧的变量,如下面的例子所示:
|
|
如果在等于号 =
前面再添加乘性运算符、加性运算符或位运算符,就可以完成复合赋值操作。这种复合赋值操作相当于是对下面常规表达式的简写形式:
|
|
每个主要算术运算符(以及个别的其他运算符)都有对应的复合赋值运算符。这些运算符如下所示:
- 乘/赋值
*=
; - 除/赋值
/=
; - 模/赋值
%=
; - 加/赋值
+=
; - 减/赋值
-=
; - 左移/赋值
<<=
; - 有符号右移/赋值
>>=
; - 无符号右移/赋值
>>>=
。
设计这些运算符的主要目的就是简化赋值操作,使用它们不会带来任何性能的提升。
条件运算符
? :
条件运算符应该算是 JavaScript 中最灵活的一种运算符了,而且它遵循与 Java 中的条件运算符相同的语法形式,如下面的例子所示:
|
|
逗号运算符
逗号运算符多用于声明多个变量;但除此之外,逗号运算符还可以用于赋值。在用于赋值时,逗号运算符总会返回表达式中的最后一项,如下面的例子所示:
|
|
由于 0
是表达式中的最后一项,因此 num
的值就是 0
。虽然逗号的这种使用方式并不常见,但这个例子可以帮我们理解逗号的这种行为。
关卡
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
更多
关注微信公众号「劼哥舍」回复「答案」,获取关卡详解。
关注 https://github.com/stone0090/javascript-lessons,获取最新动态。