JavaScript 中的長屬性存取鏈可能容易出錯,因為它們中的任何一個都可能評估為 null
或 undefined
(也稱為「nullish」值)。在每個步驟中檢查屬性是否存在很容易變成深度巢狀的 if
陳述式結構,或複製屬性存取鏈的長 if
條件
// Error prone-version, could throw.
const nameLength = db.user.name.length;
// Less error-prone, but harder to read.
let nameLength;
if (db && db.user && db.user.name)
nameLength = db.user.name.length;
以上也可以使用三元運算子表示,這並不能完全有助於可讀性
const nameLength =
(db
? (db.user
? (db.user.name
? db.user.name.length
: undefined)
: undefined)
: undefined);
介紹可選鏈接運算子 #
您肯定不想要撰寫這樣的程式碼,因此有其他替代方案是可取的。其他一些語言提供了一個優雅的解決方案來解決這個問題,使用稱為「可選鏈接」的功能。根據 最近的規格提案,「可選鏈接是由一個或多個屬性存取和函式呼叫組成的鏈,第一個以 ?.
令牌開頭」。
使用新的可選鏈接運算子,我們可以將上述範例改寫如下
// Still checks for errors and is much more readable.
const nameLength = db?.user?.name?.length;
當 db
、user
或 name
為 undefined
或 null
時會發生什麼事?使用可選鏈接運算子,JavaScript 會將 nameLength
初始化為 undefined
,而不是擲回錯誤。
請注意,此行為也比我們對 if (db && db.user && db.user.name)
的檢查更健全。例如,如果 name
總是保證為字串怎麼辦?我們可以將 name?.length
變更為 name.length
。然後,如果 name
為空字串,我們仍然會取得正確的 0
長度。這是因為空字串是偽值:它在 if
子句中表現得像 false
。可選鏈接運算子修正了這個常見的錯誤來源。
其他語法形式:呼叫和動態屬性 #
還有一個用於呼叫可選方法的運算子版本
// Extends the interface with an optional method, which is present
// only for admin users.
const adminOption = db?.user?.validateAdminAndGetPrefs?.().option;
語法可能會令人意外,因為 ?.()
是實際運算子,它套用於它之前的表達式。
運算子有第三個用法,即透過 ?.[]
執行的可選動態屬性存取。它會傳回括弧中引數所引用的值,或如果沒有物件可以取得值,則傳回 undefined
。以下是可能的用例,遵循上述範例
// Extends the capabilities of the static property access
// with a dynamically generated property name.
const optionName = 'optional setting';
const optionLength = db?.user?.preferences?.[optionName].length;
最後這個形式也可用于選擇性索引陣列,例如
// If the `usersArray` is `null` or `undefined`,
// then `userName` gracefully evaluates to `undefined`.
const userIndex = 42;
const userName = usersArray?.[userIndex].name;
當需要非 undefined
預設值時,可選鏈接運算子可以與 nullish 合併 ??
運算子 結合使用。這啟用了具有指定預設值的安全的深度屬性存取,解決了以前需要使用者空間程式庫(例如 lodash 的 _.get
)的常見用例
const object = { id: 123, names: { first: 'Alice', last: 'Smith' }};
{ // With lodash:
const firstName = _.get(object, 'names.first');
// → 'Alice'
const middleName = _.get(object, 'names.middle', '(no middle name)');
// → '(no middle name)'
}
{ // With optional chaining and nullish coalescing:
const firstName = object?.names?.first ?? '(no first name)';
// → 'Alice'
const middleName = object?.names?.middle ?? '(no middle name)';
// → '(no middle name)'
}
可選鏈結運算子的特性 #
可選鏈結運算子有幾個有趣的特性:短路、堆疊和可選刪除。讓我們透過範例來了解每個特性。
短路表示如果可選鏈結運算子提早傳回,就不會評估表達式的其他部分
// `age` is incremented only if `db` and `user` are defined.
db?.user?.grow(++age);
堆疊表示可以在一系列的屬性存取上套用多個可選鏈結運算子
// An optional chain may be followed by another optional chain.
const firstNameLength = db.users?.[42]?.names.first.length;
不過,在單一鏈結中使用多個可選鏈結運算子時,請謹慎考慮。如果保證值不會為 null,則不建議使用 ?.
來存取其屬性。在上面的範例中,db
被視為總是已定義,但 db.users
和 db.users[42]
可能不是。如果資料庫中有這樣的使用者,則假設 names.first.length
總是已定義。
可選刪除表示 delete
運算子可以與可選鏈結結合使用
// `db.user` is deleted only if `db` is defined.
delete db?.user;
更多詳細資訊可以在 提案的語意區段 中找到。