`??` 和 `||` 搞混,线上用户头像全挂了

问题场景

上周五快下班,客服群突然炸了:

"好多用户头像显示空白!" "不是全部,大概 30% 的账号头像没了"

查前端日志,头像接口返回正常,url 字段有值。再看渲染层代码:

js 复制代码
const avatarUrl = user.avatar?.url || '/default-avatar.png'

逻辑看似没毛病:有头像用头像,没有就用默认图。但诡异的是,有头像的用户也显示了默认图。

原因分析

问题出在 || 运算符的"激进"行为上。

|| 会把所有假值 (falsy) 都当成"没有"来对待。JavaScript 中的假值包括:

值 类型
'' 空字符串
0 数字零
false 布尔假
null 空
undefined 未定义
NaN 非数字

排查后发现,这批头像异常的用户的 url 值刚好是 空字符串 '' --- 头像服务在某些情况下会返回 url: ""(代表用户上传过头像但处理失败)。

js 复制代码
// 实际数据
user.avatar = { url: '' }  // 空字符串,但 ≠ 没头像

// 执行逻辑
const avatarUrl = '' || '/default-avatar.png'
// → 空字符串是假值,被 || 吞掉,返回默认图 ❌

用户明明上传过头像(数据库有记录),只是因为图片处理失败返回了空串,就被 || 强制替换成默认图------这就成了"有头像但显示默认"的 bug。

解决方案

方案一:改用 ??(空值合并运算符)

js 复制代码
const avatarUrl = user.avatar?.url ?? '/default-avatar.png'

?? 只会在左操作数为 null 或 undefined 时才取右侧值。''、0、false 都会原样保留。

注意: ?? 不能与 && 或 || 混用不括弧:

js 复制代码
// ❌ 语法错误
const a = b ?? c || d

// ✅ 正确写法
const a = (b ?? c) || d

方案二:显式判断

js 复制代码
const avatarUrl = user.avatar?.url && user.avatar.url.length > 0
  ? user.avatar.url
  : '/default-avatar.png'

更保险,但啰嗦。

方案三:后端统一语义

后端对 url 字段做约束:没有头像 → 不返回字段或返回 null;有头像(包括处理失败)都正确定义。前端只信任 ??。

深入:还有哪些地方容易踩?

场景 1:分数/数值 0

js 复制代码
// 用户完成了 0 道题
const count = data.completedCount || '无数据'
// 0 被 || 吞掉,显示"无数据" ❌
const count = data.completedCount ?? '无数据'  // ✅ 显示 0

场景 2:CSS class 拼接

js 复制代码
// 某个状态的 className 可能是空串 ''
const cls = statusClass || 'default-status'
// 空串时被覆盖 ❌
const cls = statusClass ?? 'default-status'  // ✅

场景 3:函数默认参数

js 复制代码
function setTheme(color = '#333') { ... }

setTheme('')   // '' 是假值吗?不,函数默认参数只对 undefined 生效
// → 传入 '' 不会触发默认值,color = ''

函数默认参数的行为类似 ??,只对 undefined 生效。很多人误以为它和 || 一样。

要点总结

| 运算符 | 触发替换的条件 | 适用场景 |
|--------|----------------------|-----------------|------|------------------|
| ?? | null / undefined | 后端可能缺失的字段、可选链取值 |
| ` | | ` | 所有假值 | 兜底默认值(明确想过滤空串/0) |
| 函数默认参数 | 仅 undefined | 参数缺省传值 |

一句话黄金法则:

当你要表达"没有这个数据就用默认值"时,99% 的场景应该用 ??; 只有 1% 的场景(如用户输入为空时填默认文案)才刻意用 ||。

修完这个 bug 后的复盘会上,全组把 eslint 加了一条规则:

json 复制代码
// .eslintrc
{
  "rules": {
    "no-unneeded-ternary": "error",
    "@typescript-eslint/prefer-nullish-coalescing": "error"
  }
}

并在代码 review checklist 里写死了一条:"见到 || 接变量,先想想是不是应该用 ??"。

从此,头像空白 bug 再没出现过。

相关推荐
小小龙学IT4 分钟前
Nginx 开源 Web 服务器与反向代理深度解
前端·nginx·开源
鶴哥只手遮天19 分钟前
Vue 3 核心源码深度剖析系列(v3.5.44)-第 2 章 响应式内幕(一):`effect` 与依赖追踪机制
前端·vue.js·开源
鶴哥只手遮天20 分钟前
Vue 3 核心源码深度剖析系列(v3.5.44)-第 06 章 编译器内幕(二):`transform` 管线与结构指令
前端·vue.js·开源
鶴哥只手遮天21 分钟前
Vue 3 核心源码深度剖析系列(v3.5.44)-第 09 章 编译器内幕(五):compiler-sfc的SFC解析与<script setup> 编译
前端·vue.js·开源
码银24 分钟前
【web】基于HTML/CSS,JavaScript + Three.js做3D流程图
前端·css·html
鶴哥只手遮天24 分钟前
Vue 3 核心源码深度剖析系列(v3.5.44)-第 17 章 运行时内幕(五):`runtime-dom`、自定义元素与内置组件
前端·vue.js·开源
鶴哥只手遮天24 分钟前
Vue 3 核心源码深度剖析系列(v3.5.44)-第 13 章 运行时组件系统:实例、props、emit、slots 与生命周期
前端·vue.js·开源
Highcharts.js27 分钟前
Highcharts React v5 正式发布:支持 ESM、更简洁的组件、更出色的开发体验
前端·react.js·前端框架·数据可视化·highcharts·前端可视化·esm
鶴哥只手遮天30 分钟前
Vue 3 核心源码深度剖析系列(v3.5.44)-第 10 章 compiler-sfc(二)
前端·vue.js·开源
will_net34 分钟前
Vue IIS部署 api 访问当前站点 代理跳转真实API 问题
前端·javascript·vue.js