JS空值合并运算符??与逻辑或||在非空判断中的区别

作者:闭包旅人

时间:2026-11-02

浏览:482

深入对比JavaScript中空值合并运算符(??)与逻辑或(||)在非空判断中的本质差异,剖析底层求值流程、Falsy与Nullish判定边界、典型生产Bug陷阱及选型决策指南。

文章目录

一、核心判定边界:假值(Falsy)与空值(Nullish)

在 ECMAScript 2020(ES11)引入空值合并运算符 ?? 之前,前端开发者普遍使用逻辑或 || 来为变量设置默认值。很多人初学时形成了一种习惯性认知:“当变量没有传值时,就用 || 设置备选值”。这种写法在参数是字符串且必须非空、或者数字必须大于 0 时能够正常工作,因此看似非常合理。

然而,|| 的本质并不是“判断变量是否存在”,而是执行标准的布尔逻辑运算与短路返回。当左侧表达式转换为布尔值为假(Falsy)时,它就会转向右侧求值。JavaScript 规范中的假值共有以下 8 种:

  • null 与 undefined(缺少实际值或未定义)
  • 0、-0 与 0n(数字 0 与 BigInt 零)
  • ""(长度为 0 的空字符串)
  • false(布尔值假)
  • NaN(非法数字计算结果)
  • document.all(历史遗留特性,极少直接使用)

相比之下,空值合并运算符 ?? 诞生正是为了解决这种“误伤”。它的判定标尺非常单纯:只认 nullish 值(仅包括 null 和 undefined)。只要左操作数不是 null 也不是 undefined,即使它是 0、"" 或 false,?? 都会将其视作有效值原样保留。

二、底层执行流程与短路求值机制

为了透彻理解两者的运行过程,我们需要观察 JavaScript 引擎在执行具体表达式时的求值步骤。无论是 || 还是 ??,它们都具备短路求值(Short-circuit evaluation)特性,即一旦左侧满足终止条件,就不会对右侧表达式进行求值,直接返回左侧原值。

JS空值合并运算符??与逻辑或||在非空判断中的区别
图 1:逻辑或 (||) 与空值合并 (??) 的内部求值分支与短路机制推导

两者的关键分歧发生在步骤 2 的条件检验环节:

  • 逻辑或 a || b:首先计算 a,随后调用内部抽象操作 ToBoolean(a)。如果结果为 true,立即短路返回 a 的原始值;如果为 false,继续计算 b 并返回 b 的结果。
  • 空值合并 a ?? b:首先计算 a,接着进行严格空值检查(a !== null && a !== undefined)。如果通过检查,立即短路返回 a;只有在 a 为 null 或 undefined 时,才会执行右侧的 b。
原值返回原则

请特别注意:两个运算符在短路返回时,返回的都是操作数的原始值与原始类型,而不会将它们强行转为布尔类型 true 或 false。例如 "hello" || "world" 返回的是字符串 "hello",0 ?? 100 返回的是数字 0。

三、集合归属关系与判定范围剖析

我们可以将 JavaScript 中的所有数据值划分为真值集合(Truthy)与虚值集合(Falsy)。从集合论的角度来看,空值(Nullish)并不是与虚值平行的概念,而是完全包含在虚值空间之内的一个极小子集。

JS空值合并运算符??与逻辑或||在非空判断中的区别
图 2:JavaScript 值的集合归属与两类运算符的拦截边界

通过上图可以清晰地看到:

  1. 在真值区域内,|| 和 ?? 表现完全一致,都会直接返回左侧的真值。
  2. 在空值子集(null、undefined)内,|| 和 ?? 表现也完全一致,均会回退到右侧默认值。
  3. 真正的冲突与区别,集中在非空虚值区域(0、空字符串 ""、false、NaN)。|| 机械地把该区域的值全部当成“缺失”进行替换,而 ?? 则准确识别出它们是“已经存在且有具体意义的值”。

四、生产环境典型踩坑场景与对比分析

在日常开发中,由于混淆 || 与 ?? 导致的 Bug 往往隐蔽且棘手。以下四个典型场景展示了这种差异在具体代码中的实际影响:

1. 数值 0 被意外覆盖:分页与计数

在处理列表分页或计数器时,数值 0 是非常普遍的合法值,例如第 0 页、0 条评论、库存剩余 0 件。

// 场景:从接口或入参获取商品库存
function getStockDisplay(stock) {
  // 错误写法:使用 || 导致库存为 0 时显示默认数量 10
  const displayOr = stock || 10;

  // 正确写法:使用 ?? 仅在未提供库存(null/undefined)时才回退
  const displayNullish = stock ?? 10;

  return { displayOr, displayNullish };
}

console.log(getStockDisplay(0));
// 结果:displayOr 为 10(❌ 致命业务错误:明明卖光了却显示有货)
// 结果:displayNullish 为 0(✅ 准确呈现库存为0)

2. 布尔开关 false 逻辑反转

配置项中经常包含布尔开关。如果一个功能的默认值是启用(true),而用户在配置中显式指定了关闭(false),使用 || 将导致配置完全无法关闭。

// 场景:应用初始化配置
function initLogger(options = {}) {
  // 错误:当传入 { enabled: false } 时,false || true 结果依然是 true
  const enabledWithOr = options.enabled || true;

  // 正确:false 既非 null 也非 undefined,精准保留用户传入的 false
  const enabledWithNullish = options.enabled ?? true;

  console.log("Or结果:", enabledWithOr);         // true(❌ 用户无法关闭日志)
  console.log("??结果:", enabledWithNullish);     // false(✅ 忠实执行用户配置)
}

initLogger({ enabled: false });

3. 文本输入框允许清空为空字符串

在表单编辑场景中,用户可能希望清空之前填写的简介或昵称,此时提交的有效值是空字符串 ""。

// 场景:用户资料更新
const userBio = ""; // 用户手动清空了个性签名

const resultOr = userBio || "这个人很懒,什么都没写";
const resultNullish = userBio ?? "这个人很懒,什么都没写";

console.log(resultOr);      // "这个人很懒,什么都没写"(❌ 无法清空)
console.log(resultNullish); // ""(✅ 允许空内容保存)
JS空值合并运算符??与逻辑或||在非空判断中的区别
图 3:五种常见输入状态下的求值对照与业务后果评估

4. 关于 NaN 的特殊注意事项

虽然 ?? 极大改善了非空判断的准确性,但有一个细节需要特别注意:NaN(Not-a-Number)。在 JavaScript 规范中,NaN 属于 number 类型,它既不是 null 也不是 undefined。

因此,当遇到异常数学计算产生的 NaN 时,?? 会将其直接返回,而不会触发右侧的默认值回退。如果业务逻辑要求计算出错时必须重置为保底数值,仅靠 ?? 是不够的,应当配合 Number.isNaN() 显式进行校验:

const ratio = parseFloat("invalid_str"); // NaN

const val1 = ratio ?? 1.0; // 仍然得到 NaN,?? 不会拦截 NaN
const val2 = Number.isNaN(ratio) ? 1.0 : ratio; // 正确重置为 1.0

五、语法限制:为什么不能直接混用 && 和 ??

在编写复合条件表达式时,JavaScript 语法做出了极其严格的安全限制:严禁在没有明确括号的情况下混合使用 ?? 与 && 或 ||。

// 语法错误示范:
const value = a || b ?? c;   // ❌ SyntaxError: Unexpected token '??'
const res = a && b ?? c;     // ❌ SyntaxError: Unexpected token '??'

语言标准委员会(TC39)之所以在这里强制抛出语法错误,是因为 ?? 与逻辑运算符之间的优先级如果仅靠隐式规则处理,极易让开发者产生心理预期的偏差:到底是 (a || b) ?? c 还是 a || (b ?? c)?为了杜绝此类歧义引发的隐蔽故障,规范要求必须显式书写圆括号:

// 合法合规写法:明确表明计算优先级
const value1 = (a || b) ?? c; // 先进行逻辑或计算,若结果为 null/undefined 再回退到 c
const value2 = a || (b ?? c); // 先进行空值合并,若结果为虚值再回退到 a

与可选链(Optional Chaining)的黄金搭配

在现代化前端开发中,?? 最常见且最推荐的配合对象是可选链操作符 ?.。当深层对象属性可能不存在时,两者结合既能避免 TypeError: Cannot read properties of undefined,又能确保不会误杀有效值:

// 安全读取深层嵌套配置,并提供精准默认值
const animationDuration = config?.ui?.animation?.duration ?? 300;
const isDarkMode = userPreferences?.theme?.enableDark ?? false;

六、选型指南与决策模型

我们并不需要彻底抛弃 ||。在某些特定场景下,|| 仍然具有其独特的便利性。关键在于根据具体的业务需求制定清晰的决策方案。

JS空值合并运算符??与逻辑或||在非空判断中的区别
图 4:默认值运算符选型决策树与语法组合规范

可以遵循以下两点核心准则:

  1. 默认优先选用 ??:在涉及数据传递、API 响应解析、函数参数默认值、数值计算以及布尔开关等 90% 以上的数据逻辑处理中,?? 是最安全的选择,它完整尊重数据的原始状态。
  2. 在纯 UI 展示兜底时适度选用 ||:如果当前上下文属于页面文本渲染,并且需求明确规定“当没有内容、名字为空字符串 ""、或非法数字时,都统一展示‘暂无’”,此时 || 可以精简掉多余的判空逻辑(如 const title = article.title || "未命名文章";)。
对比维度 逻辑或 (||) 空值合并 (??)
引入规范 ECMAScript 1 (基础语言特性) ECMAScript 2020 (ES11 现代特性)
判断核心依据 Boolean(left) === true left !== null && left !== undefined
对 0 的处理 触发回退(判定为假) 保留原值(判定为有效)
对 "" 的处理 触发回退(判定为假) 保留原值(判定为有效)
对 false 的处理 触发回退(判定为假) 保留原值(判定为有效)
与 && 混用 按优先级自动隐式结合 必须显式包裹圆括号,否则报 SyntaxError
首选适用领域 UI 展示文案快速兜底、非零正数断言 数据结构取值、配置项读取、表单状态保留

实用工具推荐

电子包浆

绿化、压缩、噪点和虚拟水印,自由调节做旧程度。

使用 ›
图片压缩

选择画质和输出格式,预览压缩前后的大小。

使用 ›
相关文章