在JavaScript中長的鏈式調用可能容易出錯,因為任何一步都可能出現`null`或`undefined`(也被稱為“無效”值)。檢查每個步驟的屬性是否存在很容易變成深層次嵌套的`if`聲明或者複製屬性訪問鏈的長的`if`條件:
// 容易出錯的版本,可能拋出錯誤
const nameLength = db.user.name.length;
// 不容易出錯,但是難以閱讀
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);
介紹可選調用鏈
我們並不想寫出這樣的代碼,所以有一些代替方案是可取的。一些語言(例如swift,具體查看https://www.jianshu.com/p/5599b422afb0)針對這個問題提供了優雅的解決方案——可選調用鏈。
根據最近的規範(https://github.com/tc39/proposal-optional-chaining),“可選調用鏈是一個或多個屬性訪問和函數調用的鏈,以`?.`開頭”。
使用新的可選調用鏈,我們可以重寫上面的demo:
// 依然檢查錯誤,但是可讀性更高
const nameLength = db?.user?.name?.length;
使用可選調用鏈,當`db`,`user`,或`name`是`undefined`或者`null`的時候,`nameLength`被初始化為`undefined`,而不是像之前那樣拋出錯誤。
Note:可選調用鏈比我們自己用`if(db && db.user && db.user.name)`檢查更加健壯,例如,如果`name`是一個空字符串,可選字符串會將`name?.length`改為`name.length`然後得到正確的長度`0`,但是如果像我們之前那樣做判斷,不會得到正確的值,因為在if語句中空字符和`false`的行為相同。可選調用鏈修復了這個常見的bug。
其他的語法形式:調用和動態屬性
還有一個用於調用可選方法的運算符:
// 使用可選方法擴展接口,僅適用於管理員用戶
const adminOption = db?.user?.validateAdminAndGetPrefs?.().option;
這個語法可能有點兒出乎意料,因為這裡的運算符是`?.()`,該運算符適用於之前的表達式。
可選調用鏈還有第三種用法,即可選動態屬性訪問`?.[]`。如果對象中有該key對應的value,則返回value,否則返回`undefined`。demo如下:
// 使用動態屬性名訪問屬性對應的值
const optionName = 'optional setting';
const optionLength = db?.user?.preferences?.[optionName].length;
該用法同樣適用於可選索引數組,例如:
// 如果`userArray`是`null`或`undefined`,則`userName`被優雅的賦值為`undefined`
const userIndex = 42;
const userName = usersArray?.[userIndex].name;
可選調用鏈可以和[nullish coalescing ?? 操作符](https://github.com/tc39/proposal-nullish-coalescing)結合使用,返回一個非`undefined`的默認值。這樣可以使用指定的默認值安全的進行深層屬性訪問,解決了之前用戶需要JavaScript庫才能解決的問題,例如lodash的_.get(https://lodash.dev/docs/4.17.15#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)'
}
可選調用鏈的屬性
可選調用鏈有一些有趣的屬性:短路(short-circuiting),堆疊(stacking) 和可選刪除(optional deletion)。下面通過例子來了解這些屬性。
短路(short-circuiting)即如果可選調用鏈提前返回則不計算表達式的其餘部分:
// 只有在`db`和`user`不是`undefined`的情況下`age`才會+1
db?.user?.grow(++age);
堆疊(stacking)意味著可以在一系列屬性訪問中應用多個可選調用運算符:
// 一個可選鏈可以跟隨另一個可選鏈
const firstNameLength = db.users?.[42]?.names.first.length;
但是,考慮在一個鏈中使用多個可選調用運算符。如果一個value保證是有效的,那麼不鼓勵使用`?.`去訪問屬性。像在之前的例子中,`db`必然是定義的,但是`db.users`和`db.users[42]`可能是未定義的。如果數據庫中有這樣的用戶,那麼`name.first.length`也是始終被定義的。
可選刪除(optional deletion)就是`delete`操作符可以和可選鏈一起使用:
// 當且僅當`db`是定義過的時候刪除`db.user`
delete db?.user;
更多細節可以訪問該提案的語義部分(https://github.com/tc39/proposal-optional-chaining#semantics)。
原文請戳:https://v8.dev/features/optional-chaining
閱讀更多 跨界Coder 的文章
關鍵字: Swift語言 特性 JavaScript