在本教程中,您将了解 JavaScript 空值合并运算符 ??,它接受两个值并在第一个值是 null 或者 undefined 时返回第二个值 。

ES2020 引入了由双问号 ?? 表示的空值合并运算符。
a ?? b
由于它将 null 和 undefined 视为类似的情况,在本文中我们使用一个特殊的术语。为了简洁起见,我们将值称为“已定义”,当它既不是 null 也不是 undefined 时。
a ?? b 的结果是:
-
如果 a 已定义,则返回 a;
-
如果 a 未定义,则返回 b。
换句话说,?? 返回第一个非 null/undefined 的值,否则返回第二个。
空值合并运算符并不是什么全新的概念,它只是一个简洁的语法,用来获取两个值中的第一个“已定义”的值。
我们可以使用已经知道的运算符重写 result = a ?? b,如下所示:
|
1 |
result = (a !== null && a !== undefined) ? a : b; |
现在应该完全清楚 ?? 的作用了。让我们看看它在哪里能够派上用场。
?? 的常见用法是提供默认值。
例如,下面的代码会显示用户的值(如果它不是 null 或 undefined),否则显示 “Anonymous”:
|
1 2 3 |
let user; alert(user ?? "Anonymous"); // Anonymous (user is undefined) |
这是一个用户已分配名字的示例:
|
1 2 3 |
let user = "Alice"; let userName = user ?? "Anonymous"; alert(userName); // 输出 "Alice" |
我们也可以使用一系列 ?? 来从一组值中选择第一个非 null/undefined 的值。
假设我们有用户的数据存储在变量 firstName、lastName 或 nickName 中。如果用户没有填写对应的值,这些变量可能都是未定义的。
我们想要显示用户的名字,使用这些变量中的一个,如果它们都为 null/undefined,则显示 “Anonymous”。
我们可以使用 ?? 运算符来实现这一点:
|
1 2 3 4 5 6 |
let firstName = null; let lastName = undefined; let nickName = "Alice"; let userName = firstName ?? lastName ?? nickName ?? "Anonymous"; alert(userName); // 输出 "Alice" |
与 || 的比较
OR(||)运算符可以像 ?? 一样使用,正如前一章所描述的那样。
例如,在上面的代码中,我们可以将 ?? 替换为 ||,并仍然得到相同的结果:
|
1 2 3 4 5 6 |
let firstName = null; let lastName = undefined; let nickName = "Alice"; let userName = firstName || lastName || nickName || "Anonymous"; alert(userName); // 输出 "Alice" |
然而,|| 和 ?? 在处理值时略有不同。|| 会认为所有假值(如 0、false、""、NaN 等)都需要被替换,而 ?? 只会在遇到 null 或 undefined 时才进行替换。
换句话说,|| 不区分 false、0、空字符串 "" 和 null/undefined,它们都是“假值”(falsy values)。如果这些中的任何一个是 || 的第一个操作数,那么我们将得到第二个操作数作为结果。
然而,在实际应用中,我们可能只希望在变量为 null 或 undefined 时使用默认值。也就是说,当值确实未知或未设置时。
例如,考虑以下情况:
|
1 2 3 4 |
let height = 0; alert(height || 100); // 100 alert(height ?? 100); // 0 |
height || 100 会检查 height 是否为假值,而 0 确实是一个假值。所以,|| 的结果是第二个操作数,即 100。
height ?? 100 会检查 height 是否为 null 或 undefined,而它不是这两者中的任何一个,因此结果就是 height 本身,也就是 0。
在实际应用中,0 作为高度往往是一个有效的值,不应该被默认值替换。所以,?? 完全符合需求,能够做到恰到好处。
优先级
?? 运算符的优先级与 || 相同,它们在 MDN 表中的优先级都是 3。
这意味着,和 || 一样,空值合并运算符 ?? 在 = 和 ? 之前进行求值,但在大多数其他运算符之后进行求值,比如 + 和 *。
因此,我们可能需要在类似以下的表达式中添加括号:
|
1 2 3 4 5 6 7 |
let height = null; let width = null; // important: use parentheses let area = (height ?? 100) * (width ?? 50); alert(area); // 5000 |
没错,如果我们省略括号,由于 * 的优先级高于 ??,乘法运算会首先执行,这会导致不正确的结果。
|
1 2 3 4 5 |
// without parentheses let area = height ?? 100 * width ?? 50; // ...works this way (not what we want): let area = height ?? (100 * width) ?? 50; |
使用 ?? 与 && 或 ||
出于安全原因,JavaScript 禁止将 ?? 与 && 和 || 运算符一起使用,除非通过括号显式指定优先级。
以下代码会触发语法错误:
|
1 |
let x = 1 && 2 ?? 3; // Syntax error |
这个限制确实可以争论,它被添加到语言规范中是为了避免编程错误,特别是在人们从 || 转到 ?? 时。
使用显式的括号来绕过这个限制:
|
1 2 3 |
let x = (1 && 2) ?? 3; // Works alert(x); // 2 |
短路空值合并运算符
跟逻辑或和与运算符类似,如果第一个操作数不是undefined 或者是 null ,空值合并运算符也不计算第二个值。“短路空值合并运算符” 是对 ?? 运算符的一种描述,强调它的短路特性。与其他逻辑运算符(如 && 和 ||)一样,?? 运算符也具有短路行为,即当左侧操作数已确定结果时,右侧操作数不会被计算。
例如:
|
1 2 3 4 5 6 7 |
let user = null; let userName = user ?? "Anonymous"; // user 为 null,返回 "Anonymous" console.log(userName); // 输出 "Anonymous" user = "Alice"; userName = user ?? "Anonymous"; // user 已定义,返回 "Alice" console.log(userName); // 输出 "Alice" |
在上面的例子中,user ?? "Anonymous" 会先检查 user 是否为 null 或 undefined。如果 user 为 null 或 undefined,则返回默认值 "Anonymous";否则,返回 user 本身的值。所以它是一个短路操作,只有当左侧值为 null 或 undefined 时,右侧的默认值才会被使用。
总结
-
空值合并运算符
??提供了一种简洁的方式,从一组值中选择第一个“已定义”的值。 -
它用于为变量赋予默认值:
12// set height=100, if height is null or undefinedheight = height ?? 100; - 运算符
??的优先级很低,仅略高于?和=,因此在表达式中使用时考虑添加括号。 - 在没有显式括号的情况下,禁止将其与
||或&&一起使用。