可選鏈接

發佈於 · 標籤為 ECMAScript ES2020

JavaScript 中的長屬性存取鏈可能容易出錯,因為它們中的任何一個都可能評估為 nullundefined(也稱為「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;

dbusernameundefinednull 時會發生什麼事?使用可選鏈接運算子,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.usersdb.users[42] 可能不是。如果資料庫中有這樣的使用者,則假設 names.first.length 總是已定義。

可選刪除表示 delete 運算子可以與可選鏈結結合使用

// `db.user` is deleted only if `db` is defined.
delete db?.user;

更多詳細資訊可以在 提案的語意區段 中找到。

支援可選鏈結 #