||
、 &&
是JS一直就有的运算符,虽然是逻辑运算符,但是我们经常拿来用于取值和赋值操作。而 ??
和 ?.
运算符是ES6加进来的, ??
叫做“空值合并运算符”, ?.
叫做“链式运算符”,接下来我们逐一介绍。
||
和&&
运算符一个是逻辑“或”,一个是逻辑“与”。你可不要被它的名字蒙蔽了,它们虽然叫逻辑运算符,但是他们跟C和Java这些强类型语言的逻辑运算符可不同,它们最显著的区别是:
JS逻辑运算符的操作数和返回值都可以不是布尔值
这是因为JS有些值虽然不是boolean
类型,但是它们都可以当做“真值”和“假值”,我们常见的假值有false
、null
、undefined
、''
、0
、NaN
,而其他的都可以作为真值。
我们用真值和假值来解释||
和&&
的作用:
a || b
若a
为真值则返回a
,否则返回b
。a && b
若a
为假值则返回b
,否则返回a
。这是只有两个操作数的情况,如果操作数很多,比如a || b || c
,我们可以这么来解释:
||
返回第一个真值,如果没有则返回最后一个值&&
返回第一个假值,如果没有则返回最后一个值我们往往借助||
在几个值中寻找一个真值,至于真值是哪一个值可能不重要,比如:
let val = val1 || val2 || val3;
但是由于val1~3
,可能都是假值,所以val
最终可能也是个假值,所以我们在使用val
的时候仍需非空判断:
let val = val1 || val2 || val3;
if (val) {
// todo
}
另外,||
在取真值的时候还可以定义优先级,比如val1 || val2 || val3
,它们的优先级为val1 > val2 > val3
,即使三个数都为真,因为“短路效应”,也只返回第一个真值。这个特性有个普遍的应用场景:默认值。
function query(offset, limit) {
offset = offset || 1;
limit = limit || 10;
// todo
}
我们把默认值放到||
表达式的最后,用来“兜底”,这个在日常开发中屡见不鲜。
说完了||
,我们再说&&
。
&&
运算符遇到假值结束并返回假值,或者返回最后一个值。但是通常情况下,假值是没有意义的,所以我们通常的做法是想返回最后一个值,这里也有一个非常普遍的场景:链式调用。
let street = user && user.address && user.address.street;
现在是不是对||
和&&
有了全新的认识。
说??
之前,我们回顾一下||
,||
是取真值,而0
、''
、false
、NaN
都是假值,都会被||
无情“抛弃”。
但是这几个值它们都是“有值”,“有值”跟null
和undefined
还是有区别的。有些时候我们需要把它们当成“有意义”的值看待,比如0
作为金额就是有意义的值,这个时候||
运算符就无能为力了。
如果你不嫌麻烦,当然可以使用三元运算符:
function hasVal(val) {
return val !== null && val !== undefined;
}
let val = hasVal(va1) ? va1 : (hasVal(val2) ? va2 : val3);
我嫌麻烦,所以我选择话??
,??
和||
的区别就是:||
取真值,而??
取有值。
a ?? b
的含义是如果a
不为null
或undefined
则返回a
,否则返回b
。
举个例子:
let val1 = 0;
let val2 = 'hello';
val1 || val2; // 返回'hello',因为0为假值
val2 ?? val2; // 返回0,因为0为“有值”
所以??
弥补了||
不能返回0
、false
、''
、NaN
这几个“有值”的缺点。
第一次见到?.
这个运算符,还是在某乎上看讨论kotlin和java哪个好的文章,知道了kotlin有?.
这个符号来避免NPE,没想到ES6中也引入了这个特性。
在讲?.
之前,我们再次回顾一下之前的链式调用:
let street = user && user.address && user.address.street;
上面的情况,“链条”还不是很长,使用&&
还能接受,如果我们把链条拉长,像下面这样:
let street = user && user.address
&& user.address.region;
&& user.address.region.district
&& user.address.region.district.street;
我还刻意格式化了一下代码,如果不格式化,可想多么痛苦。
这个时候我们就可以使用?.
来优化了,这个运算符很好理解,我们知道user.address
中.
是用来调用属性的,而?.
表示若.
前面有值(不为null
和undefined
)才调用,没有值返回undefined
。
改写之后的代码:
let street = user?.address?.region?.district?.street;
真是简洁且优雅。
最后,码字不易,还望评论支持。