ts中 ?? 和 || 区别

在JavaScript和TypeScript开发中,我们经常需要为变量设置默认值。??(空值合并运算符)和 ||(逻辑或运算符)是两个常用的选择,但它们的判断逻辑有着本质区别。本文将深入解析这两个运算符的区别,帮助你写出更严谨的代码。

核心区别速览

|| 会在左侧值为 任何假值(falsy) 时返回右侧值,而 ?? 仅在左侧值为 nullundefined 时才返回右侧值。

这是两者最根本的差异,也是理解它们行为的关键。

JavaScript 中的假值(falsy)完整列表

在深入之前,先了解 JavaScript 中哪些值被视为假值。JavaScript 中一共有 7 个假值

  • false
  • 0(包括 -00n
  • ""(空字符串)
  • null
  • undefined
  • NaN

除了这 7 个值以外,其他所有值在布尔上下文中都被视为真值(truthy)。

核心对比:数值、字符串与布尔值场景

下面的对比表格清晰地展示了 ??|| 在不同输入值下的行为差异:

| 输入值 a | a ?? "default" | a || "default" |

|-----------|-----------------|-----------------|

| null | "default" | "default" |

| undefined | "default" | "default" |

| 0 | 0 | "default" |

| "" | "" | "default" |

| false | false | "default" |

| NaN | NaN | "default" |

| "hello" 等真值 | "hello" | "hello" |

可以看到,当 0""falseNaN 这些假值出现时,|| 会错误地使用默认值,而 ?? 则会保留这些原本有意义的值。

下面通过具体代码来深入理解:

1. 处理数字 0------最常见的坑

typescript 复制代码
// 用户可能真的想设置音量/页码为 0
const userVolume = 0;

// ❌ 使用 ||:0 被当作无效值
const volume1 = userVolume || 50;      // 结果:50(用户音量被覆盖!)

// ✅ 使用 ??:0 被正确保留
const volume2 = userVolume ?? 50;      // 结果:0(符合预期)

在使用 || 时,0 被视为假值,因此返回了默认值 50,这可能完全违背了用户的意图------比如用户想静音或跳转到第 0 页。

2. 处理空字符串

typescript 复制代码
const userInput = "";

// ❌ 使用 ||:空字符串被当作无效输入
const name1 = userInput || "匿名用户";    // 结果:"匿名用户"

// ✅ 使用 ??:空字符串被正确保留
const name2 = userInput ?? "匿名用户";    // 结果:""

空字符串可能代表用户主动清空了输入框,使用 || 会错误地将其替换为默认值。

3. 处理布尔值 false

typescript 复制代码
const isEnabled = false;

// ❌ 使用 ||:false 被当作无效值
const flag1 = isEnabled || true;    // 结果:true(覆盖了 false)

// ✅ 使用 ??:false 被正确保留
const flag2 = isEnabled ?? true;    // 结果:false(符合预期)

false 本身是一个有效的布尔值,在使用 || 提供默认值时会被错误覆盖。

4. 处理 NaN

typescript 复制代码
const calculatedValue = NaN;

// ❌ 使用 ||:NaN 被当作无效值
const value1 = calculatedValue || 100;    // 结果:100

// ✅ 使用 ??:NaN 被正确保留
const value2 = calculatedValue ?? 100;    // 结果:NaN

实际开发场景:该用哪个?

场景一:分页/计数类数值------推荐 ??

typescript 复制代码
// 当前页码为 0(第 1 页)是有效值
const currentPage = requestParams.page ?? 1;

// 评论数量为 0 是有效值
const commentCount = apiResponse.comments ?? 0;

0 在这些场景中是合法的业务数据,应该被保留。

场景二:表单输入处理------推荐 ??

typescript 复制代码
// 温度传感器可能返回 0℃------这是有效读数
const temperature = sensorValue ?? 20;

// 字体大小可能为 0(表示最小字号)
const fontSize = userSettings.fontSize ?? 16;

场景三:字符串空值判断------考虑 ||

typescript 复制代码
// 用户名输入:空字符串应显示为"匿名用户"
const displayName = username || "匿名用户";

// 或者显式处理三种情况
const displayName = username?.trim() || "匿名用户";

当空字符串和 null/undefined 都应被视为无效时,|| 是合适的选择。

场景四:布尔值开关------推荐 ??

typescript 复制代码
// 用户偏好设置:false 表示"已禁用",不应被默认值覆盖
const darkMode = userPreference.darkMode ?? true;

场景五:Vue 模板中的使用

vue 复制代码
<template>
  <!-- 留言内容:空字符串显示占位文案 -->
  <div>{{ message || '暂无留言' }}</div>
  
  <!-- 评分:0 分是有效分数 -->
  <div>评分:{{ rating ?? '未评分' }}</div>
</template>

<script setup lang="ts">
import { ref } from 'vue';

const message = ref('');
const rating = ref(0);
</script>

模板中同样需要区分 ??|| 的场景。

进阶篇:??=||= 的区别

TypeScript 4.0+ 还引入了逻辑赋值运算符,它们的行为与对应的二元运算符一致:

  • ||=:左侧为任何假值时赋值
  • ??=:左侧为 nullundefined 时才赋值
typescript 复制代码
let config1 = { volume: 0 };
let config2 = { volume: 0 };

config1.volume ||= 50;    // volume 变成 50(0 被覆盖)
config2.volume ??= 50;    // volume 保持 0(0 被保留)

??= 能更精确地控制赋值的触发条件。

兼容性与注意事项

版本要求

??ES2020(ES11) 引入的新特性,在 TypeScript 3.7+ 中可用。

现代浏览器(Chrome 80+、Firefox 72+、Safari 13.1+)和 Node.js 14+ 均已支持。对于需要兼容旧环境的项目,可以通过 Babel 等工具进行转译。

语法限制:不能与 &&|| 直接混用

出于语法歧义的考虑,ES 规范不允许 ??&&|| 直接组合使用,否则会抛出语法错误:

typescript 复制代码
// ❌ 语法错误!
a || b ?? c;
a ?? b || c;

// ✅ 正确写法:使用括号明确优先级
(a || b) ?? c;
a ?? (b || c);

总结与选择建议

  1. 核心记忆

    • ?? = 只认 nullundefined("空值")
    • || = 认所有假值(false0""nullundefinedNaN
  2. 简单原则 :当 0""false 在你的业务场景中是有意义的值时,优先使用 ??

  3. ESLint 建议 :许多现代项目配置了 @typescript-eslint/prefer-nullish-coalescing 规则,鼓励使用 ?? 替代 || 来处理空值,因为它能更精确地表达意图,避免因假值判断而引入 bug。

  4. 团队协作 :在项目中统一使用 ?? 处理空值可以提升代码可读性,降低因假值判断不一致而引入的潜在风险。

相关推荐
盖丽男19 小时前
彻底搞懂:前端MVVM、后端MVC、DDD极致面向对象的区别与落地真相
前端·mvc
澄江静如练_19 小时前
vue2中使用provide和inject出现的问题
前端·vue
星辰徐哥20 小时前
表单优化:AI驱动HTML5表单的智能验证与提示功能
前端·人工智能·html5
普通网友20 小时前
HTML5新增了哪些重要标签?多多学习也是成长的一部分
前端·学习·html5
2501_9064676320 小时前
html5网页中如何实现内网大文件的加密下载?
前端·html·html5·vue上传解决方案·vue断点续传·vue分片上传下载·vue分块上传下载
何何____20 小时前
css变换语法介绍及案例展示
前端·css
冴羽yayujs21 小时前
GitHub 前端热榜项目 - 日榜(2026-05-07)
前端·github
深蓝海拓21 小时前
用HSL颜色系统改造qdarkstyle样式表库
前端·笔记·python·qt·学习
wuxia211821 小时前
Web全栈开发案例教程(AI辅助版)
前端
MonkeyKing715521 小时前
Flutter Riverpod 2.x 设计思想与最佳实践
前端·flutter