開始接 API 之後,我很常看到這些判斷:
value ?? "-"
value || "-"
data?.customer?.name
if (value != null) {
...
}
以前我會覺得:
怎麼每個人都在防空值,而且寫法還不一樣?
後來才發現,問題就在於:
「沒有值」其實不只一種。
undefined
null
""
0
它們看起來都很像「沒東西」,但意思不一樣。
先簡單理解:
undefined
→ 還沒有被賦值 / 找不到這個值
null
→ 明確表示「目前沒有值」
""
→ 有一個 String,只是內容是空的
0
→ 有一個 Number,而且值就是 0
所以:
0
不是「沒有值」。
它是一個合法的數字。
同樣地:
""
也不是 undefined。
它只是空字串。
undefined 是什麼?例如:
let name;
console.log(name);
結果:
undefined
因為我們宣告了:
name
但還沒有給它內容。
也可能是讀取不存在的欄位:
const user = {
name: "小潔",
};
console.log(user.age);
結果:
undefined
因為:
user 裡沒有 age
null 又是什麼?null 通常是開發者或 API 明確放進去的:
const birthday = null;
可以理解成:
這個欄位存在,但目前沒有資料。
例如 API:
{
"name": "小潔",
"phone": null
}
這裡不是:
phone 不存在
而是:
phone 這個欄位存在
但是目前沒有值
所以 null 跟 undefined 雖然都代表「沒有實際內容」,語意還是不太一樣。
"" 是真的有值例如:
const name = "";
這裡:
typeof name
會是:
string
所以:
""
不是沒有資料型別。
它是一個真正的 String,只是長度是 0。
這在表單裡很常見。
例如使用者把 Input 清空後:
value = ""
有些 API 可能希望收到:
{
"description": ""
}
但有些 API 卻希望:
{
"description": null
}
甚至可能希望整個欄位不要送。
這三種意思可能完全不同。
0 更不能隨便當成空值假設商品折扣:
const discount = 0;
0 代表:
折扣就是 0。
不是:
沒有折扣資料。
但如果寫:
const display = discount || "-";
結果會變成:
-
因為 JavaScript 會把:
0
視為 falsy。
這就是很容易踩的坑。
|| 到底在判斷什麼?例如:
const value = 0;
console.log(value || "-");
結果:
-
因為 || 不是只判斷 null 或 undefined。
它會把所有 falsy 值都當成前面不成立。
常見 falsy 值:
false
0
""
null
undefined
NaN
所以:
value || "-"
比較像:
如果 value 是 falsy,就使用
"-"。
??例如:
const value = 0;
console.log(value ?? "-");
結果是:
0
因為 ?? 只會處理:
null
undefined
流程可以理解成:
value 是 null 或 undefined 嗎?
是
→ 使用右邊
不是
→ 保留原本 value
所以:
0 ?? "-"
得到:
0
而:
"" ?? "-"
得到:
""
|| 跟 ?? 最重要的差別可以直接看這張表:
| value | value || "-" | value ?? "-" |
| --- | --- | --- |
| undefined | "-" | "-" |
| null | "-" | "-" |
| "" | "-" | "" |
| 0 | "-" | 0 |
| false | "-" | false |
所以如果:
0
""
false
都是有意義的合法值,
通常:
??
會比:
||
更安全。
?. 又是在防什麼?假設 API Response:
const user = {
company: null,
};
直接寫:
user.company.name
會出錯。
因為:
company = null
後面不能繼續取:
.name
所以可以寫:
user.company?.name
意思是:
如果 company 不是 null / undefined,就繼續取 name。
如果:
company = null
結果會是:
undefined
而不是直接噴錯。
例如:
data?.customer?.company?.name
可以理解成:
data 有嗎?
↓
有
customer 有嗎?
↓
有
company 有嗎?
↓
有
name
只要中間任何一層是:
null
undefined
就停止,回傳:
undefined
!= null 為什麼可以同時擋兩個?這也是我以前很容易搞混的地方。
例如:
if (value != null) {
...
}
這裡故意使用:
!=
而不是:
!==
因為 JavaScript 裡:
null == undefined
結果是:
true
所以:
value != null
可以拿來判斷:
value 不是 null,也不是 undefined。
例如:
0 != null
// true
"" != null
// true
false != null
// true
但:
null != null
// false
undefined != null
// false
所以這種寫法其實很實用:
if (value != null) {
// 這裡的 value 不會是 null 或 undefined
}
if (value) 不一樣例如:
const value = 0;
如果:
if (value) {
...
}
不會進去。
因為:
0
是 falsy。
但:
if (value != null) {
...
}
會進去。
因為:
0 不是 null
也不是 undefined
所以:
if (value)
問的是:
這個值 truthy 嗎?
而:
if (value != null)
問的是:
這個值是不是有存在,不是 null / undefined?
這兩件事不一樣。
假設 API Response:
{
"discount": 0,
"description": "",
"supplier": null
}
這三個欄位其實代表:
discount = 0
→ 有值,而且就是 0
description = ""
→ 有值,只是空字串
supplier = null
→ 明確沒有 supplier
如果全部都寫:
value || "-"
畫面可能全部顯示:
-
但這樣就把三種不同狀態混在一起了。
例如:
discount ?? "-"
會正確保留:
0
假設使用者清空一個欄位。
Frontend Form 得到:
description = "";
但 Backend API 的規格可能是:
null
→ 清除這個欄位
undefined
→ 不修改這個欄位
""
→ 儲存空字串
那這三個結果就完全不同。
例如 PATCH:
{
description: undefined
}
某些 Request 序列化後可能根本不會送出這個欄位。
Backend 就可能理解成:
不修改 description。
但:
{
description: null
}
如果 API 有支援,可能代表:
請把 description 清空。
所以 Debug PATCH 時,我現在會特別確認:
Form 現在是什麼值?
↓
Request Payload 實際送什麼?
↓
空字串?
null?
undefined?
還是根本沒有這個 key?
這往往不是小細節,而是直接影響資料到底會不會被更新。
以前:
這個值是空的嗎?
現在我會再多問一步:
它到底是哪一種空?
undefined?
null?
""?
0?
false?
因為它們在 JavaScript 裡:
型別不同
語意不同
判斷結果也不同
?.
→ 中間可能是 null / undefined
→ 安全往下取值
??
→ 只有 null / undefined 才使用預設值
||
→ falsy 都會使用預設值
→ 包含 0、""、false
value != null
→ 同時排除 null 和 undefined
→ 但保留 0、""、false
所以:
data?.discount ?? "-"
可以一步一步讀成:
data 有嗎?
↓
如果有,拿 discount
discount 是 null / undefined 嗎?
↓
是 → "-"
不是 → 保留原值
如果:
discount = 0
最後仍然會顯示:
0
這也是為什麼看起來只是幾個小符號,
到了真正的 API、表單和資料顯示裡,卻很容易直接造成 Bug。
下一篇可以接一個跟今天非常相關的實務問題:
為什麼明明把 Input 清空了,PATCH Request 卻沒有真的把資料清掉?undefined、null 和 Request Payload 到底發生了什麼?