可选链

发布日期 · 标签:ECMAScript ES2020

JavaScript 中长长的属性访问链可能容易出错,因为其中任何一个都可能计算为 nullundefined(也称为“空值”)。检查每个步骤的属性是否存在很容易变成一个深度嵌套的 if 语句结构,或者一个复制属性访问链的长 if 条件。

// Error prone-version, could throw.
const nameLength = db.user.name.length;

// Less error-prone, but harder to read.
let nameLength;
if (db && db.user && db.user.name)
nameLength = db.user.name.length;

以上也可以用三元运算符来表达,但这并不利于可读性。

const nameLength =
(db
? (db.user
? (db.user.name
? db.user.name.length
: undefined)
: undefined)
: undefined);

介绍可选链运算符 #

你肯定不想写这样的代码,所以有一些替代方案是可取的。一些其他语言为这个问题提供了一个优雅的解决方案,使用一个称为“可选链”的功能。根据 最近的规范提案,“可选链是一个或多个属性访问和函数调用的链,第一个以标记 ?. 开始”。

使用新的可选链运算符,我们可以将上面的示例改写如下

// Still checks for errors and is much more readable.
const nameLength = db?.user?.name?.length;

dbusernameundefinednull 时会发生什么?使用可选链运算符,JavaScript 会将 nameLength 初始化为 undefined,而不是抛出错误。

注意,这种行为也比我们对 if (db && db.user && db.user.name) 的检查更健壮。例如,如果 name 始终保证为字符串?我们可以将 name?.length 更改为 name.length。然后,如果 name 是一个空字符串,我们仍然会得到正确的 0 长度。这是因为空字符串是一个假值:它在 if 子句中表现得像 false。可选链运算符修复了这个常见的错误来源。

其他语法形式:调用和动态属性 #

还有一个用于调用可选方法的运算符版本

// Extends the interface with an optional method, which is present
// only for admin users.
const adminOption = db?.user?.validateAdminAndGetPrefs?.().option;

语法可能感觉意外,因为 ?.() 是实际的运算符,它应用于它之前的表达式。

运算符的第三种用法是可选动态属性访问,它通过 ?.[] 完成。它要么返回方括号中参数引用的值,要么返回 undefined(如果没有对象可以从中获取值)。以下是一个可能的用例,遵循上面的示例

// Extends the capabilities of the static property access
// with a dynamically generated property name.
const optionName = 'optional setting';
const optionLength = db?.user?.preferences?.[optionName].length;

最后一种形式也可以用于可选地索引数组,例如

// If the `usersArray` is `null` or `undefined`,
// then `userName` gracefully evaluates to `undefined`.
const userIndex = 42;
const userName = usersArray?.[userIndex].name;

当需要非 undefined 默认值时,可选链运算符可以与 空值合并 ?? 运算符 结合使用。这使得使用指定的默认值进行安全的深度属性访问成为可能,解决了以前需要用户端库(如 lodash 的 _.get)的常见用例。

const object = { id: 123, names: { first: 'Alice', last: 'Smith' }};

{ // With lodash:
const firstName = _.get(object, 'names.first');
// → 'Alice'

const middleName = _.get(object, 'names.middle', '(no middle name)');
// → '(no middle name)'
}

{ // With optional chaining and nullish coalescing:
const firstName = object?.names?.first ?? '(no first name)';
// → 'Alice'

const middleName = object?.names?.middle ?? '(no middle name)';
// → '(no middle name)'
}

可选链运算符的属性 #

可选链运算符具有一些有趣的属性:短路堆叠可选删除。让我们通过一个例子来了解每个例子。

短路意味着如果可选链运算符提前返回,则不评估表达式的其余部分

// `age` is incremented only if `db` and `user` are defined.
db?.user?.grow(++age);

堆叠意味着可以在一系列属性访问上应用多个可选链运算符

// An optional chain may be followed by another optional chain.
const firstNameLength = db.users?.[42]?.names.first.length;

尽管如此,请谨慎使用单个链中的多个可选链运算符。如果一个值保证不为空值,那么不建议使用 ?. 来访问它的属性。在上面的示例中,db 被认为始终定义,但 db.usersdb.users[42] 可能没有定义。如果数据库中存在这样的用户,那么 names.first.length 被认为始终定义。

可选删除意味着 delete 运算符可以与可选链结合使用

// `db.user` is deleted only if `db` is defined.
delete db?.user;

更多详细信息可以在 提案的语义部分 中找到。

对可选链的支持 #