可选链式操作符 ?. 是一种安全的方式来访问嵌套的对象属性,即使中间属性不存在。
“不存在的属性”问题
如果你刚开始阅读教程并学习 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…)