对一个空值进行属性读取会让程序抛异常,在多级嵌套的对象中连续读取属性值的时候尤其容易出现这个问题。为了保证程序的健壮性,我们需要先确保当前值不为空,然后再读取下一级的值;又或者是查找 DOM,找不到的话它的值会是 null ,我们需要先确定查找到的值不为空然后再调用对应的元素方法,如此就免不了要做一连串的条件判断:

const isAdult = person && person.age && person.age >= 18;

const canvas = document.querySelector('canvas');
if (canvas) {
  const ctx = canvas.getContext('2d')
}

上面的 && 是判断值非空时常用的写法,只有当 && 左边的值为真时,它右边的语句才会执行,这也正是它的问题所在,因为在 JavaScript 中,空字符串 '' 被视为假值,当它跟 && 一起使用的时候,就会得到意想不到的结果:

const text = '';
const textLength = text && text.length; // ''

类似上面的代码,当我们想要获取一个文本长度的时候,空字符串得到的值应该是 0,但是 '' 被视为假值直接返回了,从而得到的是 '' 自身。这并不是我们想要的结果。

可选链 Optional Chaining

从 _ES2020 起(TypeScript3.7 开始)_,我们可以使用新的 可选链(Optional Chaining) 语法来获得更好的体验。它主要有以下几种形式:

obj?.prop  // 静态属性
obj?.[expr]  // 动态属性
arr?.[index]  // 数组
func?.(args) // 函数调用

它的意义是:当 ?. 左边的值不为 undefined 或者 null 时,才执行右边的语句,否则返回 undefined

let name = User?.name;

// 相当于

let name = (User === null || User === undefined) ? undefined : User.name;

文章开头的两个例子可以改成以下的形式:

const isAdult = person?.age >= 18;

const canvas = document.querySelector('canvas');
const ctx = canvas?.getContext('2d');

动态属性

const propName = 'key_' + 'a';
const valueA = obj?.[propName];

函数调用

除属性非空校验外,我们也可以对方法进行校验,当对象存在该方法时才会执行,当然如果函数存在同名的属性而不是一个可执行的方法时,对它进行调用还是免不了抛异常的。

const canvas = document.querySelector('canvas');
const ctx = canvas?.getContext.?('2d');

const obj = { a: 2, b: () => {} };
obj?.b(); // ok
obj?.a(); // TypeError;

数组索引

const firstData = dataList?.[0];

短路运算

let index = 0;
let nextItem = dataList?.[++index];

在可选链执行时,遵循短路运算的逻辑,当 ?. 左边的为 null 或者 undefined 时,右边的并不会被计算,也就是 index 不会加 1 。

跟空值合并运算符 (nullish-coalescing) 结合起来用可以让我们很方便地指定一个默认值:

const userName = User?.name ?? 'Unknown';

目前不支持的一些用法

new a?.();
new a?.`string`
a?.b = c; // 可选的属性赋值;
super?.();
new?.target