⚠️⚠️最近新增的特性
以下所述操作符都是最近添加到 JavaScript 的特性。 旧式浏览器可能需要 polyfills。
可选链操作符 ( ?. )
可选链操作符 ( ?. ) 允许读取位于连接对象链深处的属性的值,而不必明确验证链中的每个引用是否有效。?. 操作符的功能类似于 . 链式操作符,不同之处在于,在引用为空 (nullish ) (null 或者 undefined) 的情况下不会引起错误,该表达式短路返回值是 undefined。与函数调用一起使用时,如果给定的函数不存在,则返回 undefined。
语法
?. 在ES2020 中被引入,用法如下:
obj?.prop
obj?.[expr]
arr?.[index]
func?.(args)
“不存在的属性”的问题(TypeError错误)
如果你刚开始学习 JavaScript,那可能还没接触到这个问题,但它却相当常见。
举个例子,假设我们有很多个 user 对象,其中存储了我们的用户数据。
我们大多数用户的地址都存储在 user.address 中,街道地址存储在 user.address.street 中,但有些用户没有提供这些信息。
在这种情况下,当我们尝试获取 user.address.street,而该用户恰好没提供地址信息,我们则会收到一个错误:
let user = {}; // 一个没有 "address" 属性的 user 对象
console.log(user.address.street); // Error!
这是预期的结果。JavaScript 的工作原理就是这样的。因为 user.address 为 undefined,尝试读取 user.address.street 会失败,并收到一个错误。
处理报错问题,可能最先想到的方案是在访问该值的属性之前,使用 if 或条件运算符 ?: 对该值进行检查。
- 使用嵌套的三元运算符 :
let user = {}; // user 没有 address 属性
console.log(user.address ? user.address.street ? user.address.street.name : null : null); // null
- 使用 if 进行空值检查:
let user = {}; // user 没有 address 属性
if(user.address && user.address.street){
console.log(user.address.street.name); // 未执行(不报错)
}
- 使用 && 运算符:
let user = {}; // user 没有 address 属性
console.log( user.address && user.address.street && user.address.street.name ); // undefined(不报错)
?. 示例
如果可选链 ?. 前面的值为 undefined 或者 null,它会停止运算并返回 undefined。
使用 ?.读取 user 对象的 address 属性:
let user = {}; // user 没有 address 属性
console.log( user?.address?.street ); // undefined(不报错)
请注意:?. 语法使其前面的值成为可选值,但不会对其后面的起作用。
⚠️⚠️不要过度使用可选链
我们应该只将
?.使用在一些东西可以不存在的地方。例如,如果根据我们的代码逻辑,
user对象必须存在,但address是可选的,那么我们应该这样写user.address?.street,而不是这样user?.address?.street。那么,如果
user恰巧为 undefined,我们会看到一个编程错误并修复它。否则,如果我们滥用?.,会导致代码中的错误在不应该被消除的地方消除了,这会导致调试更加困难。
⚠️⚠️
?.前的变量必须已声明如果未声明变量
user,那么user?.anything会触发一个错误:// ReferenceError: user is not defined user?.address;
?.前的变量必须已声明(例如let/const/var user或作为一个函数参数)。可选链仅适用于已声明的变量。
其它变体:?.(),?.[]
可选链 ?. 不是一个运算符,而是一个特殊的语法结构。它还可以与函数和方括号一起使用。
例如,将 ?.() 用于调用一个可能不存在的函数。
let user = {}; // user 没有 address 属性
console.log(user.someNonExistentMethod?.());
// expected output: undefined
let key = "firstName";
let user = {}; // user 没有 address 属性
console.log(user?.[key]);
// expected output: undefined
⚠️⚠️我们可以使用 ?. 来安全地读取或删除,但不能写入
可选链 ?. 不能用在赋值语句的左侧。
let user = null; user?.name = "John"; // Error,不起作用 // 因为它在计算的是:undefined = "John"
空值合并运算符 ( ?? )
空值合并操作符(??)是一个逻辑操作符,当左侧的操作数为 null 或者 undefined 时,返回其右侧操作数,否则返回左侧操作数。
语法
?? 在ES2020 中被引入,用法如下:
leftExpr ?? rightExpr
?? 示例
使用空值合并操作符为变量赋默认值:
const nullValue = null;
const emptyText = ""; // 空字符串,是一个假值,Boolean("") === false
const someNumber = 42;
const valA = nullValue ?? "valA 的默认值";
const valB = emptyText ?? "valB 的默认值";
const valC = someNumber ?? 0;
console.log(valA); // "valA 的默认值"
console.log(valB); // ""(空字符串虽然是假值,但不是 null 或者 undefined)
console.log(valC); // 42
当左表达式不为 null 或 undefined 时,不会对右表达式进行求值
function A() { console.log('函数 A 被调用了'); return undefined; }
function B() { console.log('函数 B 被调用了'); return false; }
function C() { console.log('函数 C 被调用了'); return "foo"; }
console.log( A() ?? C() );
// 依次打印 "函数 A 被调用了"、"函数 C 被调用了"、"foo"
// A() 返回了 undefined,所以操作符两边的表达式都被执行了
console.log( B() ?? C() );
// 依次打印 "函数 B 被调用了"、"false"
// B() 返回了 false(既不是 null 也不是 undefined)
// 所以右侧表达式没有被执行
与 ( || ) 比较
它们之间重要的区别是:
||返回第一个 真 值。??返回第一个 已定义的 值。
在 JavaScript 中,undefined、null、NaN、0、""、false 都返回 假 值(falsy values),而其他值都为 真 值。不过在实际中,我们可能只想在变量的值为 null/undefined 时使用默认值。
例如,下面这种情况:
let height = 0;
console.log(height || 100); // 100
console.log(height ?? 100); // 0
( ?? ) 优先级
?? 运算符的优先级与 || 相同,它们的的优先级都为 4,详见:MDN。
⚠️⚠️将
??直接与 AND(&&)和 OR(||)操作符组合使用是不可取的。(译者注:应当是因为空值合并操作符和其他逻辑操作符之间的运算优先级/运算顺序是未定义的)这种情况下会抛出SyntaxError。
null || undefined ?? "foo"; // 抛出 SyntaxError
true || undefined ?? "foo"; // 抛出 SyntaxError
(null || undefined ) ?? "foo"; // 返回 "foo"
与可选链式操作符(?.)的关系
空值合并操作符针对 undefined 与 null 这两个值,可选链式操作符(?.) 也是如此。在这访问属性可能为 undefined 与 null 的对象时,可选链式操作符非常有用。