← 回到手记
代码

?? 和 ?.:默认值别再被 0 和空字符串坑了

用 ?? 代替 || 给默认值,不再误伤 0、空字符串和 false;再配合 ?. 和 ??= 安全取值、补默认字段。

要解决的问题

前端代码里到处都在「给个默认值」:音量没设置就用 50,昵称为空就显示「游客」,接口没返回分页大小就用 20。很多人顺手写成 value || 默认值,平时没事,一碰到 0、空字符串 ""、false 就出 bug——用户明明把音量调到 0,页面却又给他放到 50。

另一个高频场景是取深层属性:user.profile.city,只要 profile 是 null,整行就抛 TypeError。以前只能写 user.profile && user.profile.city 一层层判断。

JavaScript 的 空值合并运算符 ??、可选链 ?. 和 空值合并赋值 ??= 就是为这两件事准备的。前两者从 ES2020 起进入标准,??= 属于 ES2021;按 MDN 的兼容性说明,?? 和 ?. 自 2020 年 7 月起、??= 自 2020 年 9 月起已在主流浏览器可用(Baseline Widely available)。本文示例在本机 Node.js v20.19.2 实际运行验证过。

核心概念

1. || 看的是「真假」,?? 看的是「有没有值」

MDN 的说明大意是:|| 会把左边先转成布尔值,只要是假值(0、''、NaN、false 等)就返回右边;而 ?? 只有在左边是 null 或 undefined 时才返回右边,其它值都原样保留。

2. ?. 遇到 null/undefined 就短路

a?.b 的意思是:如果 a 是 null 或 undefined,整个表达式直接得到 undefined,不再往后算、也不报错;否则就正常取 a.b。它有三种写法:obj?.prop、obj?.[expr]、func?.(args)。

3. ??= 只在「没有值」时才赋值

x ??= y 相当于 x ?? (x = y)(只是 x 只求值一次)。左边已经有值时,右边根本不会执行。

示例代码

// 1. || 的坑:0、""、false 都被当成「没有」
const settings = { volume: 0, nickname: "", autoplay: false };
console.log(settings.volume || 50);      // 50   ← 用户设的 0 丢了
console.log(settings.nickname || "游客"); // 游客
console.log(settings.autoplay || true);  // true ← 用户关掉的又被打开

// 2. ?? 只认 null / undefined
console.log(settings.volume ?? 50);      // 0
console.log(settings.nickname ?? "游客"); // ""(空字符串被保留)
console.log(settings.autoplay ?? true);  // false
console.log(settings.missing ?? "默认"); // 默认

// 3. ?. 安全取深层属性,常和 ?? 搭配
const user = { profile: null };
console.log(user.profile?.city);             // undefined,不报错
console.log(user.profile?.city ?? "未填写"); // 未填写
console.log(user.getName?.());               // undefined(方法不存在就不调用)
const list = null;
console.log(list?.[0]);                      // undefined

// 4. ??= 给配置补默认值
function normalize(options) {
  options.pageSize ??= 20;
  options.keyword ??= "";
  return options;
}
console.log(normalize({ pageSize: 0 }));   // { pageSize: 0, keyword: '' }
console.log(normalize({ keyword: null })); // { keyword: '', pageSize: 20 }

一句话选择:「0、空字符串、false 也算有效值」就用 ??;「所有假值都要换成默认」才用 ||。 表单数字、开关、计数器这类场景,基本都该用 ??。

常见误区

误区一:?? 和 ||、&& 直接混写。 规范不允许不加括号混用,会直接报语法错误:

null || undefined ?? "foo";   // SyntaxError
(null || undefined) ?? "foo"; // "foo",加括号就行

误区二:以为解构默认值也能挡住 null。 解构默认值只在属性不存在或值为 undefined 时生效,null 会原样通过:

const { size = 10 } = { size: null };
console.log(size); // null,不是 10

接口经常返回 null,这时用 ?? 或 ??= 更稳。

误区三:以为 ?.() 什么都能兜住。 obj.run?.() 只在 run 是 null/undefined 时跳过;如果 run 存在但不是函数(比如是数字 42),照样抛 TypeError。

误区四:给可选链加括号。 短路只作用于一条连续的链。(obj?.a).b 里括号把链截断了,obj 为 null 时 (obj?.a) 得到 undefined,再取 .b 就报错:

const o = null;
o?.a.b;    // undefined,整条链都短路了
(o?.a).b;  // TypeError: Cannot read properties of undefined (reading 'b')

误区五:用 ?. 赋值。 obj?.prop = 1 是语法错误,可选链只能用来读,不能放在赋值号左边。另外它也不能用于未声明的变量:undeclaredVar?.prop 仍会抛 ReferenceError。

误区六:到处滥用 ?.。 如果某个对象按业务逻辑「必须存在」,用 ?. 会把本该暴露的 bug 悄悄变成 undefined,问题推迟到更远的地方才爆。只在「确实可能没有」的地方用。

小结

  • 默认值优先考虑 ??,它只替换 null 和 undefined,不会误伤 0、""、false。
  • 深层取值用 ?.,常见组合是 a?.b?.c ?? 默认值。
  • 给对象补默认字段用 ??=,比解构默认值多挡住一个 null。
  • ?? 不能和 ||、&& 裸混,要加括号;可选链不能用来赋值,也别随手加括号截断。

参考资料

✳

谢谢你读到这里。