?? 和 || 搞混,線上使用者頭像全掛了 問題場景
上週五快下班,客服群突然炸了:
「好多使用者頭像顯示空白!」
「不是全部,大概 30% 的帳號頭像沒了」
查前端日誌,頭像 API 回傳正常,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;有頭像(包括處理失敗)都正確定義。前端只信任 ??。
js 代碼解讀複製代碼// 使用者完成了 0 道題
const count = data.completedCount || '無資料'
// 0 被 || 吞掉,顯示「無資料」❌
const count = data.completedCount ?? '無資料' // ✅ 顯示 0
js 代碼解讀複製代碼// 某個狀態的 className 可能是空字串 ''
const cls = statusClass || 'default-status'
// 空字串時被覆蓋 ❌
const cls = statusClass ?? 'default-status' // ✅
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"
}
}
並在 code review checklist 裡寫死了一條:「看到 || 接變數,先想想是不是應該用 ??」。
從此,頭像空白 bug 再沒出現過。