先记住这个答案
??(空值合并运算符)和 || 都能给默认值,区别在于判断范围。|| 对左侧做真假判断,所有假值(0、false、空字符串、null、undefined、NaN)都会触发回退。?? 只看左侧是不是 null 或 undefined,是的话才用右侧的默认值,0、false、空字符串这些合法数据不会被覆盖。
??只管null和undefined||还会覆盖0、false、空字符串??和||不加括号混用会报语法错误??=只在当前值为空值时才赋值
用代码看清差异
把几种常见的假值和空值同时交给 || 和 ??,就能直观看到两者的区别。
|| 和 ?? 对同一组输入的结果JavaScriptconsole.log(0 || 100, 0 ?? 100);
console.log(false || true, false ?? true);
console.log('' || 'default', JSON.stringify('' ?? 'default'));
console.log(null || 'fallback', null ?? 'fallback');
console.log(undefined || 'fallback', undefined ?? 'fallback');查看输出与解释
100 0
true false
default ""
fallback fallback
fallback fallback前三行是关键:|| 把 0、false、空字符串都替换成了默认值,而 ?? 保留了它们。最后两行 null 和 undefined 的结果一致,因为它们既是假值也是空值,两个运算符都会回退。
第三行的 ?? 结果是空字符串,示例用 JSON.stringify 把它显示成 ""。空字符串是合法值,?? 不会替换它。
什么时候该用哪个
选择标准很简单:如果 0、false、空字符串在你的场景里是合法数据,用 ??。如果你确实想把所有假值都替换成默认值,用 ||。
比如音量控制:用户设置了音量为 0,表示静音。如果用 volume || 50,静音设置会被覆盖成 50。用 volume ?? 50,只有用户没设置过(undefined)或者清空了设置(null)时才回退到 50。
再比如表单里的复选框:checked || true 会把用户主动关闭的 false 覆盖成 true。用 checked ?? true 就不会。
实际项目中大多数给默认值的场景都应该用 ??,因为"没有值"通常指的是 null 和 undefined,不包括 0 和 false。|| 更适合的场景是你确实想排除所有假值,比如 displayName || 'Anonymous'——即使用户名是空字符串也应该显示默认名。
混用限制和 ??= 赋值
?? 不能和 && 或 || 在同一个表达式里不加括号直接混用,否则会抛 SyntaxError。这是语言设计者故意的限制,避免读者猜优先级。
写成 (a ?? b) || c 或 a ?? (b || c) 加上括号就合法了,但要想清楚语义:前者先用 ?? 选出值,再用 || 过滤假值;后者先用 || 过滤,再用 ?? 兜底。两种写法的结果可能不同。
ES2021 还引入了 ??= 赋值运算符,obj.value ??= 'default' 等价于 if (obj.value === null || obj.value === undefined) obj.value = 'default'。它只在当前值为空值时才赋值,已有 0、false、空字符串不会被覆盖。
容易答错的地方
- 以为
??返回布尔值 - 和
||一样,??返回的是左值或右值本身,不是true或false。0 ?? 100返回0(number类型),不是true。 - 忽略
NaN不是 nullish NaN既不是null也不是undefined,所以NaN ?? 100返回NaN。如果你的计算可能产生NaN,还需要用Number.isNaN单独检查,??挡不住它。
面试官还会怎么问?
??= 和 ||= 有什么区别?
x ??= y 在 x 为 null 或 undefined 时赋值。x ||= y 在 x 为假值时赋值。区别和 ?? 与 || 一样:??= 不动 0 和空字符串,||= 会。
可选链 ?. 和 ?? 怎样配合?
常见写法是 user.profile?.name ?? '匿名'。?. 在链路中遇到 null 或 undefined 时短路返回 undefined,然后 ?? 接上默认值。两个运算符配合可以安全地深入嵌套对象并给兜底值。
为什么不直接扩展 || 的行为,而是新增了 ???
改 || 的行为会破坏大量已有代码。很多老代码就是利用 || 对假值的覆盖特性来工作的,比如 name || 'default'。新增一个运算符可以让开发者按需选择,不影响已有的逻辑。
参考资料
示例用于理解所注明的运行环境与边界;延伸学习可结合原文中的更多案例。