Menu

JavaScript 空值合并运算符(??):正确设置默认值

?? 运算符如何只在值为 null 或 undefined 时才使用默认值,以及为什么在大多数实际场景中它比 || 更好。

本页包含可运行的编辑器。编辑、运行,立即查看输出。

更聪明的默认值

JavaScript 一直有一个设置默认值的捷径:value || fallback。它大多数时候都能用,但自带一个 bug:它把任何假值都当作缺失。0、'' 和 false 都会触发后备值,即使它们才是真正的答案。

空值合并运算符 ?? 解决了这个问题。它只在 null 或 undefined 时才使用后备值:

0 和 '' 被保留下来,null 和 undefined 则不会。这个运算符的全部内容就是这些。

为什么 || 不够用

下面是 ?? 想要避免的经典 bug:

用户要的音量是 0(静音),昵称是空的。两个值都被悄悄覆盖了,因为 || 分不清“缺失”和“假值”。

换成 ??:

现在 0 和 '' 原样通过,只有真正缺失的字段才会得到默认值。这几乎总是你想要的效果。

理解思路

?? 只问一个问题:左侧是 null 或 undefined 吗?其他情况都不算。

左侧的值left || right 返回left ?? right 返回
null右侧右侧
undefined右侧右侧
0右侧左侧(0)
''右侧左侧('')
false右侧左侧(false)
NaN右侧左侧(NaN)
任意对象左侧左侧

当数据中的假值有实际意义时,选 ??。当你确实想排除所有假值时(短字符串、为零的计数等),选 ||。这种情况确实存在,但比人们以为的要少。

短路求值

和 ||、&& 一样,?? 也会短路。如果左侧不是空值,右侧根本不会执行:

expensiveDefault() 只运行了一次,是为 b 运行的。当后备值是一次函数调用、一次网络请求,或者其他在不需要时最好跳过的操作时,这一点很有用。

与可选链搭配

?? 和 ?. 在 ES2020 中一起推出,本来就是为了配合使用而设计的。可选链沿着一条可能缺失的路径访问,任何一步缺失都会得到 undefined;空值合并再补上一个合理的默认值:

没有这两个运算符,同样的代码就是一长串难看的 && 判断,或者一个 try/catch。有了它们,安全访问和合理的默认值一行就能写完。

空值赋值:??=

a ??= b 只在 a 为 null 或 undefined 时把 b 赋给 a。它是逻辑空值赋值运算符,同样会短路:如果 a 已经有值,右侧不会被求值。

注意 verbose: false 和 retries: 0 都被保留了:??= 只补全真正缺失的值。对比一下 ||=,它会把这两个值都覆盖掉。

优先级与括号规则

?? 故意不允许在没有括号的情况下和 || 或 && 混用。下面的代码会抛出 SyntaxError:

const x = a || b ?? c;   // SyntaxError

语言设计者认为这里的优先级足够容易引起歧义,值得强制你写清楚。加上括号就没问题了:

分组不同,结果也不同。括号不是装饰:它在告诉以后读代码的人(以及解析器)你的本意。

与默认参数不同

函数签名中的默认参数有自己的规则:只在实参为 undefined 时生效,null 不会触发。这和同等对待两者的 ?? 有细微的差别。

如果希望 null 也能触发后备值,就在函数体内使用 ??:

区别虽小,但当某个 API 用 null 表示“未设置值”时,很容易在这里栽跟头。

什么时候用 ??

除非有特别的理由,否则就把 ?? 当作设置默认值的首选运算符。它最符合大多数数据的实际情况:0、'' 和 false 通常都是值得保留的真实值,只有真正缺失的数据才需要后备值。|| 留给那些确实想替换掉任何假值的场景。

下一章:类

对象和数组的内容就到这里,你已经掌握了安全地构建和访问数据结构的工具。下一章转向组织行为:类、继承,以及它们底层的原型系统。

常见问题

JavaScript 中的空值合并运算符是什么?

?? 只在左侧为 null 或 undefined 时返回右侧的值,否则原样返回左侧的值。value ?? fallback 是设置默认值的标准写法,不会误伤 0 或 '' 这类虽为假值但合法的值。

JavaScript 中 ?? 和 || 有什么区别?

|| 遇到任何假值都会使用后备值:0、''、false、NaN、null、undefined。?? 只在 null 和 undefined 时使用后备值。如果 0 或空字符串在你的数据中是合法的值,就用 ??。如果你确实想排除所有假值,|| 仍然是正确的选择。

空值合并可以和可选链一起用吗?

可以,而且这是很常见的搭配。user?.settings?.theme ?? 'light' 会安全地沿属性链访问,中途任何一环缺失都会得到 undefined,然后换成 'light'。这两个运算符在 ES2020 中一起推出,正是为了支持这种写法。

??= 运算符有什么作用?

a ??= b 只在 a 当前为 null 或 undefined 时把 b 赋给 a。它是逻辑空值赋值运算符,是 a = a ?? b 的简写,但带有短路效果:如果 a 已经有值,右侧根本不会被求值。用来给选项对象补全缺失的默认值很方便。

Coddy 编程语言插图

和 Coddy 一起学编程

开始