JavaScript 中的可选链运算符
2020年05月28日
在 ES2020 之前,如果要访问 JavaScript 中对象的嵌套属性,则必须在每个级别检查是否为 null 或 undefined,否则最终将会抛出 TypeError。
为了避免出现 TypeError,我们将不得不创建临时变量或执行一系列增量 && 调用,这看起来很丑陋,并且同时占用了空间和时间。
例如:
const obj = {
prop: {
a: "value"
}
}
// before ES2020 - no checks
obj.foo.a // TypeError: Cannot read property 'a' of undefined
// before ES2020 - incremental nested checks
obj.foo && obj.foo.b // undefined
在ES2020中,通过可选链运算符 ?.
,现在我们就可以内联进行这些检查了。
可选链运算符的基本用法
可选链运算符的语法很简单,只需在下一个访问 .
之前添加一个 ?
即可。
引用我们之前的示例,这是使用可选链运算符之后的操作:
const obj = {
prop: {
a: "value"
}
}
// Optional Chaining
obj.foo?.a // undefined
如你所见,如果表达式返回 null 或 undefined,则可选链运算符的左侧将始终返回 undefined。
原理
那么,可选链运算符实际上发生了什么?我使用 Babel REPL 检查了转换后的代码。
让我们来弄清楚引擎下到底发生了什么。
使用此输入:
const obj = {}
obj.foo?.a
我们会得到以下输出:
var _obj$foo;
const obj = {};
(_obj$foo = obj.foo) === null || _obj$foo === void 0 ? void 0 : _obj$foo.a;
让我们用 undefined 替换 void 0 位,并清理变量名。然后,我们获得了更具可读性的代码段。
Babel 创建一个指向对象键的临时变量,将其与 null 和 undefined 进行比较,如果所有这些测试均通过,则最终返回结果值。
var foo;
const obj = {};
(foo = obj.foo) === null || foo === undefined ? undefined : foo.a;
以下是可选链运算符的其他用例:
方法和数组
可选链运算符适用于方法和数组以及静态属性,语法几乎相同。只要记住地方.
之前使用?
,但在调用数组或函数之前,语法如下所示:obj.foo?.()
或 obj.array?.[x]
下面是一个例子:
const obj = {
list: [1,2,3],
func: () => console.log('Hi'),
}
// before ES2020 - no checks
obj.bar[1] // TypeError: Cannot read property '1' of undefined
obj.baz() // TypeError: obj.baz is not a function
// before ES2020 - incremental nested checks
obj.bar && obj.bar[1] // undefined
obj.baz && obj.baz() // undefined
// Optional Chaining
obj.bar?.[1] // undefined
obj.baz?.() // undefined
有关顶级访问权限的重要说明
使用可选链运算符时必须知道顶级对象必须存在,这一点很重要!也就是说,在我们之前的案例中,obj 本身必须实际存在,然后才能在可选链中向前看。
例如,下面的代码将引发错误。请注意,在我们的上下文中不存在 doesnNotExist。因此,即使使用可选链运算符,我们也会出现错误。
doesnNotExist?.property // ReferenceError: doesnNotExist is not defined
现在,如果实际实例化了该变量,则即使当前为 null,undefined 或类型错误(例如数字等),也算是合法的。请参考以下示例:
const a = null;
a?.property // undefined
// As long as the first item is not undefined or null we're in good shape
const d = 24;
d.wut?.(); // undefined
最后,如果父级为 null 或 undefined,则需要第二个可选链来防止类型错误,请参考以下示例:
// function call
const b = null;
b.func?.() // TypeError: Cannot read property 'func' of null
// Notice the ?. after b
// If we add an additional optional chain we can safegaurd against b being null
b?.func?.() // undefined
// array use
// Same thing, we can safegaurd against b being undefined using the first ?.
const c = undefined;
c.array?.[100] // TypeError: Cannot read property 'array' of undefined
c?.array?.[100] // undefined
根据上述逻辑,我的建议是:当你在顶层对象上调用方法或数组时总是添加一个额外的?
。总而言之:
// good idea
topLevel?.func?.()
topLevel?.arr?.[1]
// potentially could throw an error if topLevel is null or undefined
topLevel.func?.()
topLevel.arr?.[1]
短链
假设你的查询没有返回 undefined,但是你想检测其他代码。你可以在同一表达式中完成所有操作,这是一个短链的例子:
const data = {
list: [1,2,3]
}
// Short Circuiting
data.list?.reverse() // [3, 2, 1]
data.items?.reverse() // undefined
长链
很棒的是,你可以嵌套长链的可选链,非常适合深层嵌套的对象。在这种情况下,嵌套可选链运算符如下所示:
obj?.parent?.child?.name // undefined
堆码
你可以根据需要堆叠和链接任意数量的可选链。
one?.two?.[2].three?.(3)?.four
不支持的操作
不要去尝试对所有事物进行可选链接!是的,虽然很酷,但并不是所有在 ECMA 下的东西都可以选择链接在一起。基本上,只需记住 3 种受支持的操作:属性访问器,数组和函数调用。
以下是可选链接中不支持的操作列表:
// Delete
delete a.?b // ReferenceError: Can't find variable: a
// Constructor
new a?.() // SyntaxError: Cannot call constructor in an optional chain.
// Template literals
a?.`string` // SyntaxError: Cannot use tagged templates in an optional chain.
// Property assignment
a?.b = c // SyntaxError: Left side of assignment is not a reference.
// Imports, Exports, Super, etc. etc.
可选链运算符在实际中的示例
那么,可选链运算符在实际中有什么帮助呢?
尽管这些都是琐碎的示例,但在 JavaScript 中这种情况经常发生,在 Web 浏览器的情况下甚至更多:表单值,DOM 对象属性,API 结果,对象方法,列表等等。访问嵌套属性时,我们必须始终加以保护。
这是一个更真实的例子:假设你的 API 正在返回客户数据。数据可能存在或可能不存在,并且可能包含或可能不包含 orders 数组,但是如果你要在列表中呈现这些项目。
在可选链运算符出现之前,你可能会执行以下操作:
if (customer && customer.orders) {
customer.orders.map(order => printOrder(order))
}
现在,你可以在进行其他工作之前,先检查 API 响应返回的对象。
customer?.orders?.map(order => printOrder(order))
你可以使用可选链运算符来检查 DOM 元素或 React 组件的存在,然后再访问它们的属性或调用它们的方法。
例如,假设你想在 React 组件中的 prop 上运行回调方法,但不能保证会提供它。现在,只需要在调用回调方法之前加上 ?.
即可。
props.onClick?.()
这是另一个琐碎的例子:假设你要向窗口对象添加事件侦听器,但你不能保证窗口对象是否存在,也许你正在执行服务器端渲染,并且可能在节点中。
window?.addEventlistener.('click', () => {});
假设你要在某些元素上设置属性,但不确定它们是否存在,只需使用可选链运算符。
document?.querySelector('[data-foo]')?.setAttribute('disabled', 'true');
或者,也许你正在尝试访问所有浏览器尚不支持的实验性 API。假设你要使用 replaceAll 数组方法,该方法目前仅在 Safari 中受支持,使用可选链接可以避免错误。
"pool".replaceAll?.("o", "e") // undefined or peel
开始在项目中使用可选链运算符
大多数现代浏览器已支持可选链接,为了安全并向后兼容,请使用转换器。如果你使用的是 Babel,请安装 OC 插件…
npm install @babel/plugin-proposal-optional-chaining
并将其添加到你的 .babelrc
文件中:
{
"plugins": [
"@babel/plugin-proposal-optional-chaining",
]
}
阅读有关可选链运算符的更多信息
由于可选链现在处于第 4 阶段,因此可以在 ECMAScript 上查看其文档。另外,请查看有关可选链运算符的最终 TC39提案 和 MDN 上有关可选链运算符的文档,以了解更多详细信息。
原文链接:https://seifi.org/javascript/optional-chaining-in-javascript.html
FENews 是由一群热爱技术的前端小伙伴自发组成的团队。团队会定期创作和翻译前端相关的技术文章,同时我们也欢迎外部投稿或加入我们的核心编辑团队。如果您对我们感兴趣,请关注我们的公众号:
