JavaScript 課程:運算子(Operators)— Optional Chaining(?.)
簡介
在日常的前端開發中,我們常會碰到「物件屬性深層巢狀」的情況,例如 user.profile.address.city。如果其中任何一層為 null 或 undefined,直接存取會拋出 TypeError,導致程式中斷。過去只能透過多層 if 判斷或 && 短路運算子來防護,程式碼不僅冗長,也不易閱讀。
ECMAScript 2020 引入的 Optional Chaining(可選鏈)(?.)正是為了解決這個痛點。它讓我們可以在一次存取時,同時檢查每一層是否為 null 或 undefined,若是則直接回傳 undefined,避免例外拋出。這項語法不僅提升開發效率,還能讓程式碼更簡潔、易於維護,對 初學者 與 中階開發者 都相當友善。
核心概念
1. 為什麼需要 Optional Chaining?
在沒有 ?. 前,我們必須寫類似以下的防呆程式:
let city;
if (user && user.profile && user.profile.address) {
city = user.profile.address.city;
}
如此寫法不僅冗長,還容易遺漏某層檢查,導致 隱蔽的錯誤。Optional Chaining 讓這段程式變成一行:
const city = user?.profile?.address?.city; // 若任一層為 null/undefined,結果為 undefined
重點:
?.只會在左側的值是null或undefined時停止求值,其他 falsy 值(如0、''、false)不會被攔截。
2. 基本語法
| 用法 | 說明 |
|---|---|
obj?.prop |
若 obj 為 null/undefined,回傳 undefined,否則回傳 obj.prop |
obj?.[expr] |
動態屬性存取,expr 為任意表達式 |
func?.(...args) |
呼叫函式前先檢查函式是否為 null/undefined |
new ctor?.(...args) |
建構子呼叫前的檢查(ES2021) |
範例:
const config = {
api: {
endpoint: '/v1/data',
timeout: 5000,
},
};
const timeout = config?.api?.timeout; // 5000
const retry = config?.api?.retry; // undefined (不會拋錯)
3. 與其他運算子結合
Optional Chaining 常與 Nullish Coalescing (??) 搭配使用,提供「預設值」的功能:
const timeout = config?.api?.timeout ?? 3000; // 若 timeout 為 undefined/null,使用 3000
同時,也可以與 陣列的 ?. 結合,安全取得陣列元素或方法:
const first = users?.[0]; // 若 users 為 undefined,結果為 undefined
const length = users?.length; // 若 users 為 undefined,結果為 undefined
4. 程式碼範例
以下提供 5 個實用範例,展示不同情境下的 Optional Chaining 用法。
範例 1:安全存取深層屬性
// 假設從 API 取得的資料可能缺少某些欄位
function getUserCity(user) {
// 使用 ?. 直接取得 city,若任一層不存在則回傳 undefined
return user?.profile?.address?.city;
}
// 測試
console.log(getUserCity({ profile: { address: { city: 'Taipei' } } })); // "Taipei"
console.log(getUserCity({ profile: null })); // undefined
範例 2:安全呼叫可能不存在的函式
const logger = {
info: (msg) => console.log('INFO:', msg),
// debug 可能在某些環境下不存在
};
function logDebug(message) {
// 若 logger.debug 為 undefined,什麼都不執行
logger.debug?.(message);
}
// 呼叫
logDebug('測試訊息'); // 沒有錯誤,什麼也不會印出
logger.info('一般訊息'); // "INFO: 一般訊息"
範例 3:動態屬性存取
const settings = {
theme: 'dark',
layout: 'grid',
};
function getSetting(key) {
// 使用 obj?.[expr] 讓 key 可以是任何字串
return settings?.[key];
}
console.log(getSetting('theme')); // "dark"
console.log(getSetting('unknown')); // undefined
範例 4:安全呼叫建構子(ES2021)
class Person {
constructor(name) {
this.name = name;
}
}
// 假設某些情況下不需要建立實例
function createPerson(name, shouldCreate) {
// 若 shouldCreate 為 false,Person?. 會返回 undefined
return shouldCreate ? new Person(name) : undefined;
}
const p1 = createPerson('Alice', true);
console.log(p1?.name); // "Alice"
const p2 = createPerson('Bob', false);
console.log(p2?.name); // undefined (不會拋錯)
範例 5:與 Nullish Coalescing 結合提供預設值
function getApiTimeout(config) {
// 若 timeout 為 undefined 或 null,使用 2000 作為預設
return config?.api?.timeout ?? 2000;
}
console.log(getApiTimeout({ api: { timeout: 5000 } })); // 5000
console.log(getApiTimeout({ api: {} })); // 2000
console.log(getApiTimeout(undefined)); // 2000
常見陷阱與最佳實踐
| 陷阱 | 說明 | 解決方式 |
|---|---|---|
誤把 ?. 用在左值 |
obj?.prop = value 會產生 SyntaxError,因為 Optional Chaining 只能作為 右值(取值)或 呼叫 使用。 |
若需要條件賦值,先檢查再賦值: if (obj) obj.prop = value; |
與 && 混用造成重複檢查 |
同時使用 obj?.prop && obj.prop2,obj?.prop 已經保證不為 null/undefined,再用 && 會多餘。 |
直接寫 obj?.prop?.prop2,保持語意一致。 |
對函式的 ?. 會返回 undefined |
func?.() 若 func 為 undefined,整個表達式回傳 undefined,不會拋錯。若預期返回值必為布林或數字,需自行處理預設值。 |
使用 func?.() ?? defaultValue 來確保得到期望的類型。 |
不適用於 null/undefined 之外的 falsy 值 |
0、''、false 仍會被正常返回,若想把它們視為「無值」需額外判斷。 |
結合 ??: obj?.prop ?? fallback。 |
| 過度使用降低程式可讀性 | 雖然 ?. 讓程式碼變短,但過度鏈接(如 a?.b?.c?.d?.e) 可能讓讀者難以掌握資料結構。 |
盡量在 資料模型 明確的情況下使用,或先將中間結果存入變數。 |
最佳實踐:
- 僅在需要防止
null/undefined時使用,不要把它當成「萬能」的防呆工具。 - 配合
??提供預設值,讓回傳結果更具可預測性。 - 在大型物件或 API 回傳資料時,先定義 TypeScript/Flow 型別,再結合
?.,提升開發體驗。 - 保持鏈的深度在合理範圍(3~4 層),超過時考慮重構資料結構或使用函式抽象。
實際應用場景
前端 UI 渲染
從後端取得的 JSON 可能缺少某些欄位,使用user?.profile?.avatarUrl ?? '/default.png'直接取得圖片路徑,避免 UI 因undefined而產生錯誤。React / Vue 組件 Props
組件接收的props可能未傳入,props?.title?.toUpperCase()可以安全呼叫字串方法,避免組件掛掉。第三方套件 API
某些套件在舊版不提供特定方法,使用library?.newFeature?.()讓程式在舊版環境仍能正常執行。Node.js 後端服務
讀取環境變數或設定檔時,process.env?.DB?.HOST可避免因未設定變數導致程式崩潰,並可結合??設定預設值。測試與 Mock
在單元測試中,透過mockObj?.method?.()只在 mock 提供該方法時才呼叫,減少測試程式碼的條件判斷。
總結
Optional Chaining(?.)是 ES2020 為 JavaScript 加入的強大運算子,專門解決深層屬性存取時的 null/undefined 安全問題。透過簡潔的語法,我們可以:
- 減少繁雜的防呆程式(
if、&&) - 提升程式可讀性與維護性
- 與 Nullish Coalescing (
??)、解構賦值等語法自然結合
在實務開發中,合理使用 ?. 能讓前端 UI、React/Vue 組件、Node 後端服務等各種情境下的程式碼更健壯、更易於除錯。當然,開發者仍需留意其使用範圍與限制,避免過度鏈接或誤用於左值。掌握這項運算子,將是提升 JavaScript 開發效率的重要一步。祝你在寫程式的路上,玩得開心、寫得更好!