可选链操作符与空值合并操作符

目的

因为在项目 Code Review 时发现部分新人对可选链操作符 ?. 的理解有误,特此记录一下其含义及用法;顺带提一下与其同时出现的空值合并操作符 ??

可选链操作符

MDN引用:
可选链操作符( ?. )允许读取位于连接对象链深处的属性的值,而不必明确验证链中的每个引用是否有效。?. 操作符的功能类似于 . 链式操作符,不同之处在于,在引用为空(nullish ) (null 或者 undefined) 的情况下不会引起错误,该表达式短路返回值是 undefined。与函数调用一起使用时,如果给定的函数不存在,则返回 undefined

如果上面的解释你不太明白,可以先看下面的例子:

在一个项目中,使用一些对象属性或调用对象方法时,为了安全,我们一般都会需要先判断这个对象是否存在,以及属性是否存在等;尤其当数据是由后端接口返回的情况下,更要加上一些空值判断。

比如:

1
2
3
if (res && res.data && res.data.success) {
// code
}

类似这种判断贯穿整个项目很多地方,写的时候很是繁琐;可选链操作符就是为了解决这个问题的,上面的判断改用可选链操作符的话,可以这么写:

1
2
3
if (res?.data?.success) {
// code
}

是不是简单清晰明了?

可选链的作用是判断它的前一个数据是否是 undefined 或者 null;如果是,则不再执行后续操作,直接返回 undefined;如果不是,就可以继续访问后续属性,以返回该属性的结果。

还是说上面的例子,它的实际运行情况是这样的:

1
2
3
4
5
6
7
if (
(res == null) ? undefined
: (res.data == null) ? undefined
: res.data.success
) {
// code
}

短路

可选链操作符是一种短路运算;当左侧操作数为 nullundefined 时,右侧如果有表达式,是不会被执行的。
看了上面的解析例子,你能大概理解吗?

测试:

1
2
3
4
let arr = null;
let n = 1;
let res = arr?.[n++];
console.log(n); // 1

语法

看了例子,大家应该也知道在对象属性的访问时,要如何使用可选链操作符了,但是其他如数组、函数调用该如何使用可能还不太清楚,这里也写一下

1
2
3
4
5
6
7
8
9
10
11
// 对象属性
obj?.prop;
// 关联数组语法
obj?.[expr];
// 数组索引
arr?.[n];
// 函数调用
fn?.();

// 混合
obj?.arrProp?.[3]?.fn?.();

虽然最后这个混合的例子有点儿那啥,但是也是为了让你们能更清楚的了解一下如何使用,就不要吐槽了。

不能被赋值

可选链不能用于赋值操作符的左侧,即不能给一个可选链赋值。

1
2
let obj = null;
obj?.prop = 1; // Uncaught SyntaxError: Invalid left-hand side in assignment

空值合并操作符

MDN引用:
空值合并操作符??)是一个逻辑操作符,当左侧的操作数为 null 或者 undefined 时,返回其右侧操作数,否则返回左侧操作数。

提到空值合并操作符,就必须提一下逻辑或操作符 (||),它们两个乍一看很相似,实际却是不同的。

空值合并操作符仅针对左侧操作数为 nullundefined 时,才返回右侧操作数;而逻辑或操作符则是针对左侧操作数为 falsy 时,就返回右侧操作数。
而在 JavaScript 中的 falsy 数据,不止 nullundefined 两种,其他还有 false, 0, NaN, '' 这些数据。

具体可以看下面的示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
null ?? 'default'; // 'default'
null || 'default'; // 'default'

undefined ?? -1; // -1
undefined || -1; // -1

0 ?? -1; // 0
0 || -1; // -1

false ?? true; // false
false || true; // true

'' ?? 'default string'; // ''
'' || 'default string'; // 'default string'

了解了吗?当左侧操作数为 nullundefined 时,空值合并操作符和逻辑或操作符的结果是一致的,但当左侧操作数为其他 falsy 值时,二者就不同了。

短路

类似逻辑操作符,空值合并操作符也是一个短路运算:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
function f1() {
console.log('f1');
return null;
}
function f2() {
console.log('f2');
return false;
}
function f3() {
console.log('f3');
return true;
}

f1() ?? f2() ?? f3();
// 先后打印 f1 f2 并返回 false
// 没有执行 f3

其他

空值合并运算符(??)还有一点需要注意的是,它不能和 逻辑与运算符(&&) 和 逻辑或运算符(||) 混合使用。

1
2
3
4
null || undefined ?? 1; // 抛出 SyntaxError

// 即使看上去不会被执行,这是语法错误,即不能这样书写代码
true || true ?? false; // 抛出 SyntaxError

不过如果使用圆括号将它们进行分组隔开的话,则没问题:

1
2
3
(null || undefined) ?? 1; // 1
(true || true) ?? false; // true
null || (0 ?? 1); // 0

参考资料

  • MDN-可选链操作符
  • MDN-空值合并运算符