JavaScript Optional Chaining (可選鏈運算子)
Optional Chaining(可選鏈)是 ES2020 引入的語法,使用 ?. 運算子。它讓你可以安全地存取可能不存在的巢狀物件屬性,不用擔心中間某個屬性是 null 或 undefined 而拋出錯誤。
問題背景
在存取深層巢狀物件時,經常需要檢查每一層是否存在:
var user = {
name: 'Mike',
address: {
city: 'Taipei'
}
};
// 如果 user.address 不存在,會報錯
console.log(user.address.city); // 'Taipei'
console.log(user.address.street); // undefined
console.log(user.contact.phone); // TypeError: Cannot read property 'phone' of undefined
傳統的解決方式是逐層檢查:
// 方法一:逐層檢查
var phone;
if (user && user.contact && user.contact.phone) {
phone = user.contact.phone;
}
// 方法二:使用 && 短路求值
var phone = user && user.contact && user.contact.phone;
這樣的程式碼冗長且難以閱讀。
Optional Chaining 語法
使用 ?. 可以大幅簡化:
var user = {
name: 'Mike',
address: {
city: 'Taipei'
}
};
// 如果 contact 不存在,直接回傳 undefined,不會報錯
console.log(user?.contact?.phone); // undefined
// 如果存在就正常取值
console.log(user?.address?.city); // 'Taipei'
運作原理
?. 會檢查左邊的值是否為 null 或 undefined:
- 如果是,立即回傳
undefined,不繼續往右執行 - 如果不是,繼續正常存取屬性
var a = null;
console.log(a?.b); // undefined(a 是 null)
var b = undefined;
console.log(b?.c); // undefined(b 是 undefined)
var c = { d: 1 };
console.log(c?.d); // 1(c 存在,正常存取)
不同的使用方式
存取物件屬性
var user = { name: 'Mike' };
// 點語法
user?.name; // 'Mike'
user?.address; // undefined
// 中括號語法
var prop = 'name';
user?.[prop]; // 'Mike'
呼叫方法
var user = {
greet: function() {
return 'Hello!';
}
};
// 如果方法存在就呼叫
user.greet?.(); // 'Hello!'
user.sayBye?.(); // undefined(不會報錯)
存取陣列元素
var users = [{ name: 'Mike' }, { name: 'John' }];
users?.[0]?.name; // 'Mike'
users?.[5]?.name; // undefined
實用範例
處理 API 回應
async function getUser(id) {
var response = await fetch('/api/users/' + id);
var data = await response.json();
// 安全地存取可能不存在的巢狀資料
var city = data?.user?.address?.city;
var phone = data?.user?.contact?.phones?.[0];
return {
city: city || '未提供',
phone: phone || '未提供'
};
}
存取 DOM 元素
// 如果元素不存在,不會報錯
var value = document.querySelector('#myInput')?.value;
// 安全地呼叫方法
document.querySelector('#myButton')?.addEventListener('click', handler);
設定物件的預設值
var config = getConfig();
var settings = {
theme: config?.appearance?.theme || 'light',
language: config?.locale?.language || 'zh-TW',
fontSize: config?.appearance?.fontSize ?? 16
};
處理回呼函式
function processData(data, options) {
// 如果有 onSuccess callback 就呼叫
options?.onSuccess?.(data);
// 等同於
if (options && options.onSuccess) {
options.onSuccess(data);
}
}
processData({ id: 1 }, {
onSuccess: function(data) { console.log(data); }
});
processData({ id: 2 }); // 沒有 options 也不會報錯
搭配 Nullish Coalescing
Optional Chaining 經常搭配 Nullish Coalescing 運算子 (??) 使用,提供預設值:
var user = {
settings: {
theme: null
}
};
// ?? 只在 null 或 undefined 時使用預設值
var theme = user?.settings?.theme ?? 'light';
console.log(theme); // 'light'
// 和 || 的差異
var count = user?.settings?.count ?? 0; // 0
var count = user?.settings?.count || 0; // 0
// 但如果值是 0
user.settings.count = 0;
user?.settings?.count ?? 10; // 0(保留 0)
user?.settings?.count || 10; // 10(0 被視為 falsy)
注意事項
不要過度使用
// 如果你確定某個屬性一定存在,不需要用 ?.
var name = user?.name; // 如果 user 一定存在,直接用 user.name
// 只在真的可能是 null/undefined 的地方使用
var city = user.address?.city;
不能用於賦值
var obj = {};
obj?.prop = 'value'; // SyntaxError
短路求值
?. 後面的程式碼在短路時不會執行:
var count = 0;
var obj = null;
obj?.foo(count++); // 不會執行 count++
console.log(count); // 0
瀏覽器支援
Optional Chaining 是 ES2020 的功能,現代瀏覽器都已支援。如果需要支援舊瀏覽器,可以使用 Babel 等工具轉譯。
| 瀏覽器 | 支援版本 |
|---|---|
| Chrome | 80+ |
| Firefox | 74+ |
| Safari | 13.1+ |
| Edge | 80+ |
| Node.js | 14+ |