2022 年 4 月 14 日

可选链式操作符“?. ”

最近添加的内容
这是最近添加到该语言中的内容。旧浏览器可能需要 polyfill

可选链式操作符 ?. 是一种安全的方式来访问嵌套的对象属性,即使中间属性不存在。

“不存在的属性”问题

如果你刚开始阅读教程并学习 JavaScript,那么这个问题可能还没有影响到你,但它很常见。

例如,假设我们有 user 对象,其中包含有关我们用户的的信息。

我们的大多数用户在 user.address 属性中都有地址,其中包含街道 user.address.street,但有些人没有提供地址。

在这种情况下,当我们尝试获取 user.address.street,而用户恰好没有地址时,我们会收到一个错误

let user = {}; // a user without "address" property

alert(user.address.street); // Error!

这是预期结果。JavaScript 的工作方式就是这样。由于 user.addressundefined,因此尝试获取 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 出现了三次。

这就是将可选链 ?. 添加到语言中的原因。一劳永逸地解决这个问题!

可选链

如果 ?. 之前的 undefinednull 的值,可选链 ?. 将停止评估并返回 undefined

为了简洁起见,在本文的后面,我们将说如果某个东西不是 null 且不是 undefined,则它“存在”。

换句话说,value?.prop

  • 如果 value 存在,则作为 value.prop 工作,
  • 否则(当 valueundefined/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"

总结

可选链 ?. 语法有三种形式

  1. obj?.prop – 如果 obj 存在,则返回 obj.prop,否则返回 undefined
  2. obj?.[prop] – 如果 obj 存在,则返回 obj[prop],否则返回 undefined
  3. obj.method?.() – 如果 obj.method 存在,则调用 obj.method(),否则返回 undefined

正如我们所看到的,它们都很直接且易于使用。?. 检查左侧是否为 null/undefined,如果不存在,则允许评估继续进行。

?. 的链条允许安全地访问嵌套属性。

尽管如此,我们应该谨慎地应用 ?.,仅在根据我们的代码逻辑,左侧不存在是可以接受的情况下使用。这样,如果发生编程错误,它就不会向我们隐藏这些错误。

教程地图

评论

在评论之前阅读此内容…
  • 如果您有改进建议 - 请 提交 GitHub 问题或提交请求,而不是评论。
  • 如果您无法理解文章中的某些内容 - 请详细说明。
  • 要插入几行代码,请使用 <code> 标记,对于多行 - 将它们包装在 <pre> 标记中,对于超过 10 行 - 使用沙盒 (plnkrjsbincodepen…)