Javascript 可選鏈 "?."

2023-02-17 10:44 更新

最近新增的特性

這是一個最近添加到 JavaScript 的特性。 舊式瀏覽器可能需要 polyfills.

可選鏈 ?. 是一種訪問嵌套對象屬性的安全的方式。即使中間的屬性不存在,也不會出現(xiàn)錯誤。

“不存在的屬性”的問題

如果你才剛開始讀此教程并學習 JavaScript,那可能還沒接觸到這個問題,但它卻相當常見。

舉個例子,假設我們有很多個 user 對象,其中存儲了我們的用戶數(shù)據(jù)。

我們大多數(shù)用戶的地址都存儲在 user.address 中,街道地址存儲在 user.address.street 中,但有些用戶沒有提供這些信息。

在這種情況下,當我們嘗試獲取 user.address.street,而該用戶恰好沒提供地址信息,我們則會收到一個錯誤:

let user = {}; // 一個沒有 "address" 屬性的 user 對象

alert(user.address.street); // Error!

這是預期的結(jié)果。JavaScript 的工作原理就是這樣的。因為 user.address 為 undefined,嘗試讀取 user.address.street 會失敗,并收到一個錯誤。

但是在很多實際場景中,我們更希望得到的是 undefined(表示沒有 street 屬性)而不是一個錯誤。

……還有另一個例子。在 Web 開發(fā)中,我們可以使用特殊的方法調(diào)用(例如 document.querySelector('.elem'))以對象的形式獲取一個網(wǎng)頁元素,如果沒有這種對象,則返回 null

// 如果 document.querySelector('.elem') 的結(jié)果為 null,則這里不存在這個元素
let html = document.querySelector('.elem').innerHTML; // 如果 document.querySelector('.elem') 的結(jié)果為 null,則會出現(xiàn)錯誤

同樣,如果該元素不存在,則訪問 null 的 .innerHTML 屬性時會報錯。在某些情況下,當元素的缺失是沒問題的時候,我們希望避免出現(xiàn)這種錯誤,而是接受 html = null 作為結(jié)果。

我們?nèi)绾螌崿F(xiàn)這一點呢?

可能最先想到的方案是在訪問該值的屬性之前,使用 if 或條件運算符 ? 對該值進行檢查,像這樣:

let user = {};

alert(user.address ? user.address.street : undefined);

這樣可以,這里就不會出現(xiàn)錯誤了……但是不夠優(yōu)雅。就像你所看到的,"user.address" 在代碼中出現(xiàn)了兩次。

我們看一個以相同方式獲取 document.querySelector 的例子:

let html = document.querySelector('.elem') ? document.querySelector('.elem').innerHTML : null;

我們可以看到用于進行元素搜索的 document.querySelector('.elem') 在這里實際上被調(diào)用了兩次。這樣不優(yōu)雅。

對于嵌套層次更深的屬性,代碼會變得更丑,因為需要更多的重復。

例如,讓我們以相同的方式嘗試獲取 user.address.street.name。

let user = {}; // user 沒有 address 屬性

alert(user.address ? user.address.street ? user.address.street.name : null : null);

這樣就太扯淡了,并且這可能導致寫出來的代碼很難讓別人理解。

這里有一種更好的實現(xiàn)方式,就是使用 && 運算符:

let user = {}; // user 沒有 address 屬性

alert( user.address && user.address.street && user.address.street.name ); // undefined(不報錯)

依次對整條路徑上的屬性使用與運算進行判斷,以確保所有節(jié)點是存在的(如果不存在,則停止計算),但仍然不夠優(yōu)雅。

就像你所看到的,在代碼中我們?nèi)匀恢貜蛯懥撕脦妆閷ο髮傩悦?。例如在上面的代碼中,user.address 被重復寫了三遍。

這就是為什么可選鏈 ?. 被加入到了 JavaScript 這門編程語言中。那就是徹底地解決以上所有問題!

可選鏈

如果可選鏈 ?. 前面的值為 undefined 或者 null,它會停止運算并返回 undefined

為了簡明起見,在本文接下來的內(nèi)容中,我們會說如果一個屬性既不是 null 也不是 undefined,那么它就“存在”。

換句話說,例如 value?.prop

  • 如果 ?value ?存在,則結(jié)果與 ?value.prop? 相同,
  • 否則(當 ?value ?為 ?undefined/null? 時)則返回 ?undefined?。

下面這是一種使用 ?. 安全地訪問 user.address.street 的方式:

let user = {}; // user 沒有 address 屬性

alert( user?.address?.street ); // undefined(不報錯)

代碼簡潔明了,也不用重復寫好幾遍屬性名。

這里是一個結(jié)合 document.querySelector 使用的示例:

let html = document.querySelector('.elem')?.innerHTML; // 如果沒有符合的元素,則為 undefined

即使 對象 user 不存在,使用 user?.address 來讀取地址也沒問題:

let user = null;

alert( user?.address ); // undefined
alert( user?.address.street ); // undefined

請注意:?. 語法使其前面的值成為可選值,但不會對其后面的起作用。

例如,在 user?.address.street.name 中,?. 允許 user 為 null/undefined(在這種情況下會返回 undefined)也不會報錯,但這僅對于 user。更深層次的屬性是通過常規(guī)方式訪問的。如果我們希望它們中的一些也是可選的,那么我們需要使用更多的 ?. 來替換 .。

不要過度使用可選鏈

我們應該只將 ?. 使用在一些東西可以不存在的地方。

例如,如果根據(jù)我們的代碼邏輯,user 對象必須存在,但 address 是可選的,那么我們應該這樣寫 user.address?.street,而不是這樣 user?.address?.street

那么,如果 user 恰巧為 undefined,我們會看到一個編程錯誤并修復它。否則,如果我們?yōu)E用 ?.,會導致代碼中的錯誤在不應該被消除的地方消除了,這會導致調(diào)試更加困難。

??.? 前的變量必須已聲明

如果未聲明變量 user,那么 user?.anything 會觸發(fā)一個錯誤:

// ReferenceError: user is not defined
user?.address;

?. 前的變量必須已聲明(例如 let/const/var user 或作為一個函數(shù)參數(shù))。可選鏈僅適用于已聲明的變量。

短路效應

正如前面所說的,如果 ?. 左邊部分不存在,就會立即停止運算(“短路效應”)。

因此,如果在 ?. 的右側(cè)有任何進一步的函數(shù)調(diào)用或操作,它們均不會執(zhí)行。

例如:

let user = null;
let x = 0;

user?.sayHi(x++); // 沒有 "user",因此代碼執(zhí)行沒有到達 sayHi 調(diào)用和 x++

alert(x); // 0,值沒有增加

其它變體:?.(),?.[]

可選鏈 ?. 不是一個運算符,而是一個特殊的語法結(jié)構(gòu)。它還可以與函數(shù)和方括號一起使用。

例如,將 ?.() 用于調(diào)用一個可能不存在的函數(shù)。

在下面這段代碼中,有些用戶具有 admin 方法,而有些沒有:

let userAdmin = {
  admin() {
    alert("I am admin");
  }
};

let userGuest = {};

userAdmin.admin?.(); // I am admin

userGuest.admin?.(); // 啥都沒發(fā)生(沒有這樣的方法)

在這兩行代碼中,我們首先使用點符號(userAdmin.admin)來獲取 admin 屬性,因為我們假定對象 userAdmin 存在,因此可以安全地讀取它。

然后 ?.() 會檢查它左邊的部分:如果 admin 函數(shù)存在,那么就調(diào)用運行它(對于 userAdmin)。否則(對于 userGuest)運算停止,沒有報錯。

如果我們想使用方括號 [] 而不是點符號 . 來訪問屬性,語法 ?.[] 也可以使用。跟前面的例子類似,它允許從一個可能不存在的對象上安全地讀取屬性。

let key = "firstName";

let user1 = {
  firstName: "John"
};

let user2 = null;

alert( user1?.[key] ); // John
alert( user2?.[key] ); // undefined

此外,我們還可以將 ?. 跟 delete 一起使用:

delete user?.name; // 如果 user 存在,則刪除 user.name

我們可以使用 ??.? 來安全地讀取或刪除,但不能寫入

可選鏈 ?. 不能用在賦值語句的左側(cè)。

例如:

let user = null;

user?.name = "John"; // Error,不起作用
// 因為它在計算的是:undefined = "John"

總結(jié)

可選鏈 ?. 語法有三種形式:

  1. ?obj?.prop? —— 如果 ?obj ?存在則返回 ?obj.prop?,否則返回 ?undefined?。
  2. ?obj?.[prop]? —— 如果 ?obj ?存在則返回 ?obj[prop]?,否則返回 ?undefined?。
  3. ?obj.method?.()? —— 如果 ?obj.method? 存在則調(diào)用 ?obj.method()?,否則返回 ?undefined?。

正如我們所看到的,這些語法形式用起來都很簡單直接。?. 檢查左邊部分是否為 null/undefined,如果不是則繼續(xù)運算。

?. 鏈使我們能夠安全地訪問嵌套屬性。

但是,我們應該謹慎地使用 ?.,根據(jù)我們的代碼邏輯,僅在當左側(cè)部分不存在也可接受的情況下使用為宜。以保證在代碼中有編程上的錯誤出現(xiàn)時,也不會對我們隱藏。


以上內(nèi)容是否對您有幫助:
在線筆記
App下載
App下載

掃描二維碼

下載編程獅App

公眾號
微信公眾號

編程獅公眾號