javascript中的操作符可用于表达式中,将小的表达式连接成大的表达式。本文对常见操作符的一些规则做简要的总结。
算术操作符
++ 和 —
单目,++
和--
可用于数值的自加一和自减一。操作数会被转换为number
,然后加一或减一,再赋值给操作数标识符。
整体表达式的值,取决于操作符合操作数的前后位置。如
let a = 1;
a++ // => 1,此时a === 2
--a // => 1,此时a === 1
复制代码
加号(+)
+
用作单目运算符时,可将其后的操作数转换为数值。如:
+ 1 // => 1
+ "1" // => 1
> + {} // => NaN
复制代码
用作双目运算符时可用于数值的加减和字符串的拼接。
当2个操作数都为string
或者number
时规则最简单,如下
1 + 1 // => 2
"a" + "b" // => "ab"
复制代码
其他情况下,操作符的行为如下:
- 当操作数为对象时,会先将该对象转换为原始值。转换规则大致如下:对于Date对象,先调用
toString()
,得到的结果为原始值时转换结束,否则调用valueOf()
,如果该方法返回原始值直接使用,否则会抛出类型错误。对于其他内置Js类型,也是这样的转换,不过是先尝试valueOf()
,后尝试toString()
。 - 之后,如果有
string
类型的操作数,则进行字符串的拼接,会将另一个操作数先转换为string
类型,然后进行字符串的拼接。 - 否则,两个操作数会统一转化为
number
类型,进行数值的加法运算。
示例如下:
1 + "2" // => "12"
1 + {} // => "1[object object]"
1 + [0] // => "10"
1 + null // => 1
1 + undefined // => NaN
true + true // => 2
复制代码
负号(-)
单目时将其后操作符转换为数值,然后改变符号。
- 1 // => -1
- "-1" // => 1
- {} // => NaN
复制代码
双目时,将两个操作数转换为number
,然后进行减法运算。
乘除运算(* / % **)
将两个操作数转换为number
,然后进行乘法、除法、取余或幂运算。
js中的数字都为浮点数。各种浮点数运算的结果不会被取整为整数。
其中,**
与之前的Math.pow()
功能相同,**
有右结合特性,会优先计算**
右边部分。如:
2 ** 3 ** 2 // => 2*(3*2)
复制代码
位运算符
位运算符会将操作数转化为数字,然后转换为2进制表示形式进行位运算。位运算符有 &
、|
、^
、~
、<<
、>>
、>>>
,分别会进行按位与、按位或
、按位异或、按位取非、左移、右移、零填充右移。
对一个值取非,相当于改变符号并减一。
对于一个值左移一位,相当于该值乘2。左移n位,相当于该值乘2^n。右移也是如此,会响应除2^n,并且会舍弃余数。例子如下:
0x1234 & 0x00FF // => 0x0034
0x1234 & 0x00FF // => 0x0FF0
~0x0F // => 0xFFFFFFF0
3 << 1 // => 6
7 >> 1 // => 3
-7 >> 1 // => -4
复制代码
关系操作符
=== 和 !==
===
判断两个操作数是否严格相等并返回布尔值。不做类型转换,行为如下:
- 两个值类型不同,则不相等。
- 再判断值是否相等。
NaN
属于number
,但和任何值都不相等,包括自身(所以可用x !== x
判断x
是否为NaN
)。+0
和-0
相等。- 对于
string
,需要判断长度和相同位置对应的底层字符编码是否相等。 - 对于对象引用,只有引用同一值才相等。属性是否相同不作为判断依据。
!==
和值和===
相反。
== 和 !=
==
是基于类型转换的相等判断,如果两操作数类型不同,会尝试做类型转换,然后比较。
两数类型不同时,规则如下:
- 如果两值为
null
和undefined
,则相等。 - 如果一个值为
number
,另一值为string
,则将string
转换为number
,再比较数值。 - 布尔值会转换为数值0或1。
- 如果一个值为对象,另一值为数值或字符串,则先将对象转换为原始值,再比较。
- 其他任何值的组合都不相等。
例:
null == undefined // => true
"1" == true // => true,true转换为数值1,"1"转换为数值1,相等
["0"] == 0 // => true,
复制代码
对于第三个例子,[“0”]为偏向字符串的对象,首先尝试调用自身的toString()
,结果为”0″,为基本类型,对象到原始值的转换完毕。然后将”0″转换为数值0,与右边操作数比较。
比较操作符
比较操作符包含>
、<
、>=
、<=
。其中>=
和<=
表示“不大于”和“不小于”,和==
、===
没有依赖关系。
比较符最终只会进行number
之间的比较和string
之间的比较。对于number
会比较数值的大小,对于string
会从首字母开始比较各个字母的Unicode编码值大小。如:
"abc" > "ABC" // => true,大写字母的编码值小于小写字母
复制代码
不能直接比较时,有如下规则:
- 对象值会被转换为原始值。
- 当两值都为字符串时,比较Unicode编码。
- 其他情况下,将两值转化为
number
,比较大小。NaN
和任何值比较结果都为false
。
in操作符
in
用于判断右侧对象中是否有左侧字符串属性。左侧操作数为字符串、Symbol
或可以转化为字符串的值,右侧必须为对象。返回布尔值。
示例:
let obj = {a: 1};
"a" in obj // => true
"b" in obj // => false
"toString" in obj // => true,继承的方法
let arr = ["a", "b"]
"0" in arr // => true
1 in arr // => true,1会转换为"1"
3 in arr // => false
复制代码
instanceof
instanceof
期待左侧操作数为对象,右侧操作数为对象类型,然后判断对象是否为该类型的实例,返回布尔值。
如:
let arr = [];
arr instanceof Array // => true
arr instanceof Object // => true
arr instanceof Date // => false
复制代码
instaceof
依据原型链进行判断,对于obj instaceof f
而言,会先取得f.prototype
的值,然后判断obj.__proto__
是否为f.prototype
、查找到则返回true
,否则会继续沿着原型链(obj._proto__.__proto__
)查找,直至原型链的终点null
,返回false
。所有对象都是Object
的实例。
逻辑操作符
逻辑与(&&)
&&
常用于布尔值的运算,操作数都为true
时返回true
。如:
a === 1 && b === 2
复制代码
但&&
并不要求操作数的类型。操作数为其他类型值时也有有趣的特性。JS中的值可被分为真值和假值。假值有:false
、""
、0
、undefined
、null
。其他值为真值。
&&
有短路的特性,当左操作数为真值时,返回的结果为右操作数。左操作数为假值时,返回结果为左操作数,并且不会对右操作数求值。如:
10 && 3 // => 3
1 && (console.log("r")) // 求值为右值,打印"r"。
0 && (console.log("r")) // => 0
复制代码
逻辑或(||)
类似于逻辑与,||
也有短路的特性。当左操作数为真值时,返回结果为左操作数,不对右操作数求值。当左操作数为假值时,返回结果为右操作数。如:
10 || 3 // => 10
1 || (console.log("r")) // => 1
0 || (console.log("r")) // 求值为右值,打印"r"。
复制代码
逻辑非(!)
逻辑非没有短路特性,!
会将操作数传唤为布尔值,然后取反,最终返回布尔值。一个常用的逻辑化简如下:
对一个逻辑表达式整体取反,相当于对操作数和操作符各自取反。
!(p && q) === (!p || q) // => true
复制代码
其他操作符
void
void
返回值始终为undefined
,但其之后的表达式仍会正常执行。可以用于不使用花括号的箭头函数。如:
let a = 1;
const a_pp = () => void(a++);
a_pp(); // => undefined
a; // => 2
复制代码
条件操作符(?:)
条件操作符接收三个操作数,第一个操作数会被转换为布尔值。为真时,返回第二个操作数;否则返回第三个操作数。如:
const bol = true
bol ? "1" : "0"; // => "1"
复制代码
先定义(??)
虽然它和条件操作符长得很像,但两者没有关系。是双目操作符,求值返回先定义的操作数。如果左操作数不为null
或undefined
,就返回左操作数,否则返回右操作数。如:
const apple = "apple"
apple ?? "new apple" // => "new apple"
复制代码
它也有类似于逻辑或(||
)的短路性质,可用于判断值是否有效。区别在于||需要判断左操作数为真值还是假值。在逻辑或中""
、0
、false
都是无效值。而在??
中,它们为有效值。因此可以看做是对||
的补充。如:
let str = "", num = 0;
str ?? str : "initial" // => "initial"
num ?? "exist" : "not exist" // => "exist"
复制代码
??
和&&
或||
一起使用时,需要用圆括号表明先计算哪部分,不然会报错。
条件属性访问(?.)
javascript中,null
和undefined
两个值上不存在属性。因此,通过普通的属性访问(a.b
或a[b]
)访问它们的属性会抛出TypeError。可以使用条件属性访问的方式(a?.b
或a?[b]
)避免这种错误。如:
let a;
a?.b // => undefined
复制代码
对于a?.b
,如果a
为null
或undefined
,则该表达式值就为undefined
,不会继续访问a.b
这个属性;如果a
不为null
或undefined
就会继续访问a.b
。像a.b
一样,它也可以链式访问。如:
let a = {b: null};
a?.b?.c?.d // => undefined
复制代码
同理,a?[b]
也是同样的用法,当a
为null
或undefined
时,不会访问a[b]
。自然而然的,b
所代指的表达式也不会执行求值。
条件调用(?())
类似于条件属性访问,条件调用符号为?()
,可解决a()
中a
为null
或undefined
抛出的TypeError。如:
let a;
a?.comp() // => undefined,
复制代码
a
为undefined
,表达式值直接确定为undefined
,不会真正的执行a.comp()
。函数调用中左括号左操作数也必须为函数,因此,如例子中的a
为函数之外的其他有意义变量,也会抛出TypeError。
?()
也适用于方法调用,方法调用又包含了属性访问,因此,需要区分条件属性访问和条件方法调用。如:
o.m(); // 常规属性访问,常规方法调用
o?.m(); // 条件属性访问,常规方法调用
o.m?(); // 常规属性访问,条件方法调用
o?.m?(); // 条件属性访问,条件方法调用
复制代码
因此,第一个式中,o
须为对象,且o.m
为函数。第二个式中,o
可为null
或undefined
;o
为其他值时,o.m
须为函数。第三个式中,o
须为null
和undefined
之外的的值即可。第四个式中,o
可为null
或undefined
;o
为其他值时,o.m
须为null
或undefined
或函数。