JavaScript 中长长的属性访问链可能容易出错,因为其中任何一个都可能计算为 null
或 undefined
(也称为“空值”)。检查每个步骤的属性是否存在很容易变成一个深度嵌套的 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;
当 db
、user
或 name
为 undefined
或 null
时会发生什么?使用可选链运算符,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.users
和 db.users[42]
可能没有定义。如果数据库中存在这样的用户,那么 names.first.length
被认为始终定义。
可选删除意味着 delete
运算符可以与可选链结合使用
// `db.user` is deleted only if `db` is defined.
delete db?.user;
更多详细信息可以在 提案的语义部分 中找到。