可选链式操作符 ?.
是一种安全的方式来访问嵌套的对象属性,即使中间属性不存在。
“不存在的属性”问题
如果你刚开始阅读教程并学习 JavaScript,那么这个问题可能还没有影响到你,但它很常见。
例如,假设我们有 user
对象,其中包含有关我们用户的的信息。
我们的大多数用户在 user.address
属性中都有地址,其中包含街道 user.address.street
,但有些人没有提供地址。
在这种情况下,当我们尝试获取 user.address.street
,而用户恰好没有地址时,我们会收到一个错误
let user = {}; // a user without "address" property
alert(user.address.street); // Error!
这是预期结果。JavaScript 的工作方式就是这样。由于 user.address
是 undefined
,因此尝试获取 user.address.street
会失败并出现错误。
在许多实际情况下,我们更希望在此处获取 undefined
而不是错误(表示“无街道”)。
…另一个示例。在 Web 开发中,我们可以使用特殊的方法调用(例如 document.querySelector('.elem')
)获取与网页元素相对应的对象,当不存在此类元素时,它将返回 null
。
// document.querySelector('.elem') is null if there's no element
let html = document.querySelector('.elem').innerHTML; // error if it's null
同样,如果元素不存在,我们将无法访问 null
的 .innerHTML
属性并出现错误。在某些情况下,当元素不存在是正常情况时,我们希望避免出现错误并仅接受 html = null
作为结果。
我们如何做到这一点?
显而易见的解决方案是在访问其属性之前使用 if
或条件运算符 ?
检查值,如下所示
let user = {};
alert(user.address ? user.address.street : undefined);
它有效,没有错误… 但它相当不优雅。如你所见,"user.address"
在代码中出现了两次。
以下是 document.querySelector
的相同外观
let html = document.querySelector('.elem') ? document.querySelector('.elem').innerHTML : null;
我们可以看到,元素搜索 document.querySelector('.elem')
在此处实际上被调用了两次。不好。
对于更深层次的嵌套属性,它变得更加难看,因为需要更多重复。
例如,让我们以类似的方式获取 user.address.street.name
。
let user = {}; // user has no address
alert(user.address ? user.address.street ? user.address.street.name : null : null);
这太糟糕了,人们甚至可能难以理解这样的代码。
有一种更好的方法来编写它,使用 &&
运算符
let user = {}; // user has no address
alert( user.address && user.address.street && user.address.street.name ); // undefined (no error)
对整个属性路径进行 AND 操作可确保所有组件都存在(如果不存在,则评估停止),但也不是理想的。
如你所见,属性名称仍在代码中重复。例如,在上面的代码中,user.address
出现了三次。
这就是将可选链 ?.
添加到语言中的原因。一劳永逸地解决这个问题!
可选链
如果 ?.
之前的 undefined
或 null
的值,可选链 ?.
将停止评估并返回 undefined
。
为了简洁起见,在本文的后面,我们将说如果某个东西不是 null
且不是 undefined
,则它“存在”。
换句话说,value?.prop
- 如果
value
存在,则作为value.prop
工作, - 否则(当
value
为undefined/null
时)返回undefined
。
以下是如何使用 ?.
安全地访问 user.address.street
的方法:
let user = {}; // user has no address
alert( user?.address?.street ); // undefined (no error)
代码简洁明了,完全没有重复。
以下是用 document.querySelector
的示例:
let html = document.querySelector('.elem')?.innerHTML; // will be undefined, if there's no element
即使 user
对象不存在,也可以使用 user?.address
读取地址
let user = null;
alert( user?.address ); // undefined
alert( user?.address.street ); // undefined
请注意:?.
语法使它前面的值变为可选,但不会使后面的值变为可选。
例如,在 user?.address.street.name
中,?.
允许 user
安全地为 null/undefined
(在这种情况下返回 undefined
),但这仅适用于 user
。其他属性以常规方式访问。如果我们希望其中一些属性为可选,那么我们需要用 ?.
替换更多的 .
。
我们应该只在某些内容不存在时使用 ?.
。
例如,如果根据我们的代码逻辑 user
对象必须存在,但 address
是可选的,那么我们应该编写 user.address?.street
,而不是 user?.address?.street
。
然后,如果 user
恰好是未定义的,我们将看到一个编程错误并修复它。否则,如果我们过度使用 ?.
,则在不适当的地方可能会忽略编码错误,并且更难调试。
?.
前面的变量必须声明如果根本没有变量 user
,那么 user?.anything
会触发一个错误
// ReferenceError: user is not defined
user?.address;
必须声明变量(例如 let/const/var user
或作为函数参数)。可选链仅适用于已声明的变量。
短路
如前所述,如果左侧部分不存在,?.
会立即停止(“短路”)求值。
因此,如果 ?.
右侧有任何进一步的函数调用或操作,它们将不会被执行。
例如
let user = null;
let x = 0;
user?.sayHi(x++); // no "user", so the execution doesn't reach sayHi call and x++
alert(x); // 0, value not incremented
其他变体:?.()、?.[]
可选链 ?.
不是一个运算符,而是一个特殊的语法结构,它也可以与函数和方括号一起使用。
例如,?.()
用于调用可能不存在的函数。
在下面的代码中,我们的一些用户有 admin
方法,而另一些用户没有
let userAdmin = {
admin() {
alert("I am admin");
}
};
let userGuest = {};
userAdmin.admin?.(); // I am admin
userGuest.admin?.(); // nothing happens (no such method)
这里,在这两行中,我们首先使用点(userAdmin.admin
)来获取 admin
属性,因为我们假设 user
对象存在,因此可以安全地从中读取。
然后 ?.()
检查左侧部分:如果 admin
函数存在,则它会运行(对于 userAdmin
来说是这样)。否则(对于 userGuest
),求值将停止,不会出现错误。
如果我们希望使用方括号 []
而不是点 .
来访问属性,那么 ?.[]
语法也可以使用。与以前的情况类似,它允许安全地从可能不存在的对象中读取属性。
let key = "firstName";
let user1 = {
firstName: "John"
};
let user2 = null;
alert( user1?.[key] ); // John
alert( user2?.[key] ); // undefined
我们还可以将 ?.
与 delete
一起使用
delete user?.name; // delete user.name if user exists
?.
安全地读取和删除,但不能写入可选链 ?.
在赋值的左侧没有用。
例如
let user = null;
user?.name = "John"; // Error, doesn't work
// because it evaluates to: undefined = "John"
总结
可选链 ?.
语法有三种形式
obj?.prop
– 如果obj
存在,则返回obj.prop
,否则返回undefined
。obj?.[prop]
– 如果obj
存在,则返回obj[prop]
,否则返回undefined
。obj.method?.()
– 如果obj.method
存在,则调用obj.method()
,否则返回undefined
。
正如我们所看到的,它们都很直接且易于使用。?.
检查左侧是否为 null/undefined
,如果不存在,则允许评估继续进行。
?.
的链条允许安全地访问嵌套属性。
尽管如此,我们应该谨慎地应用 ?.
,仅在根据我们的代码逻辑,左侧不存在是可以接受的情况下使用。这样,如果发生编程错误,它就不会向我们隐藏这些错误。
评论
<code>
标记,对于多行 - 将它们包装在<pre>
标记中,对于超过 10 行 - 使用沙盒 (plnkr,jsbin,codepen…)