「譯」JS新特性“可選鏈式調用”

「譯」JS新特性“可選鏈式調用”

JavaScript

在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


分享到:


相關文章: