ES 提案:可选链(optional chaining)
这篇博客文章讲述由 Gabriel Isenberg, Claude Pache, 和 Dustin Savery 提出的 ECMAScript 提案 “Optional chaining”。
概述
存在下面种类的可选操作。
obj?.prop // optional static property access
obj?.[«expr»] // optional dynamic property access
func?.(«arg0», «arg1») // optional function or method call
大概的意思是:
- 如果问号前面的值不是
undefined
或null
,那么运行问号后面的操作。 - 其它情况,返回
undefined
。
例子:访问可选静态属性
考虑下面的数据:
const persons = [
{
surname: 'Zoe',
address: {
street: {
name: 'Sesame Street',
number: '123',
},
},
},
{
surname: 'Mariner',
},
{
surname: 'Carmen',
address: {},
},
]
我们可以使用可选链安全取出 street 中的 name:
const streetNames = persons.map(p => p.address?.street?.name)
console.log(streetNames)
// => ['Sesame Street', undefined, undefined]
通过合并空值(nullish coalescing)处理默认值
合并空值操作符允许我们使用默认值 ‘(no street)’ 替代 undefined
:
const streetNames = persons.map(p => p.address?.street?.name ?? '(no name)')
console.log(streetNames)
// => ['Sesame Street', '(no name)', '(no name)']
高级特性
接下来章节介绍可选链的高级部分。
该操作符的更多详情
访问可选的静态属性
下面的两个表达式是等价的:
o?.prop
o !== undefined && o !== null ? o.prop : undefined
例子:
console.log(undefined?.prop) // undefined
console.log(null?.prop) // undefined
console.log({ prop: 1 }?.prop) // 1
访问动态属性
下面的两个表达式是等价的:
o?.[«expr»];
(o !== undefined && o !== null) ? o[«expr»] : undefined;
例子:
const key = 'prop'
console.log(undefined?.[key]) // undefined
console.log(null?.[key]) // undefined
console.log({ prop }?.[key]) // 1
执行可选的函数或方法
下面的两个表达式是等价的:
f?.(arg0, arg1)
f !== undefined && f !== null ? f(arg0, arg1) : undefined
例子:
console.log(undefined?.(123)) // undefined
console.log(null?.(123)) // undefined
console.log(String?.(123)) // "123"
注意如果这个操作符的左侧不是可以执行的时候,它将产生错误:
true?.(123) // TypeError
为什么?这个想法是该操作符仅仅允许故意的疏漏。一个不能执行的值(undefined
和 null
除外)是一个可能的错误,且应该报道,而不是无视。
短路
在属性的访问链和函数/方法的调用,执行在第一个可选操作符左侧遇到 undefined
或 null
时停止:
function isInvoked(obj) {
let invoked = false
obj?.a.b.m((invoked = true))
return invoked
}
console.log(isInvoked({ a: { b: { m() {} } } })) // true
// The left-hand side of ?. is undefined
// and the assignment is not executed
console.log(isInvoked(undefined)) // false
此行为不同于普通的操作符/函数,其中 JavaScript 在评估操作符/函数之前始终评估所有操作数/参数。这被称为短路。其它短路操作符(short-circuiting):
a && b
a || b
c ? t : e
可选链的替代方案
直到现在,在 JavaScript 中我们使用下面的方法替代可选链。
&& 操作符
下面的两个表达式大致等价:
p.address?.street?.name
p.address && p.address.street && p.address.street.name
对于每个 a && b
,b
仅仅在如果 a
是真值(truthy
[1])时被选中。a
作为 b
之前的条件或者守卫。
&& 操作符的问题
除了冗长以外,&&
还有两个问题。
首先,如果失败,&&
返回它左侧的值,然而 ?.
总是返回 undefined
:
const value = null
console.log(value && value.prop) // null
console.log(value?.prop) // undefined
第二,&&
在左侧的值为假值(falsy
[2])时都会失败,然而 ?.
仅仅在 undefined
和 null
时失败:
const value = ''
console.log(value?.length) // 0
console.log(value && value.length) // ""
注意这里,&&
返回它左侧的值是错误的相比之前的例子。
解构
原则上,你也可以使用解构操作属性访问链。但是不是很好:
for (const p of persons) {
const { address: { street: { name = undefined } = {} } = {} } = p
console.log(name === p.address?.street?.name)
}
Lodash get()
库 lodash 的方法 get()是另外一个可选链的替代方案。
举例来说,下面两个表达式是等价的:
import { get } from 'lodash-es'
p.address?.street?.name
get(p, 'address.street.name')
可选链的可用性
- babel 插件 @babel/plugin-proposal-optional-chaining。
- 其它方面的可用性,请阅读文档 MDN。
常见问题
为什么 o?.[x]
和 f?.()
两者都包含点号
下面两种可选操作的语法不是理想的:
obj?.[«expr»] // better: obj?[«expr»]
func?.(«arg0», «arg1») // better: func?(«arg0», «arg1»)
不太优雅的语法是必要的,因为区分理想语法(第一个表达式)和条件操作符(第二个表达式)太复杂了:
obj?['a', 'b', 'c'].map(x => x+x);
obj ? ['a', 'b', 'c'].map(x => x+x) : [];
为什么 null?.prop
评估为 undefined
而不是 null
?
操作符 ?.
主要关注是它的右侧:属性 .prop
是否存在?如果不存在,提前停止。因此,保持其左侧的信息很少有用。但,只有一个“提前终止”值确实简化了事情。
译者注
[1] truthy
不是 true
,详见 MDN 的解释。
[2] falsy
不是 false
,详见 MDN 的解释。
本文由 吳文俊 翻译,原文地址 ES2020: optional chaining