1. 程式人生 > 其它 >javascript 可選鏈 "?."

javascript 可選鏈 "?."

可選鏈?.是一種訪問巢狀物件屬性的安全的方式。即使中間的屬性不存在,也不會出現錯誤。

“不存在的屬性”的問題

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

舉個例子,假設我們有很多個user物件,其中儲存了我們的使用者資料。

我們大多數使用者的地址都儲存在user.address中,街道地址儲存在user.address.street中,但有些使用者沒有提供這些資訊。

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

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

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

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

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

……還有另一個例子。在 Web 開發中,我們可以使用特殊的方法呼叫(例如document.querySelector('.elem'))以物件的形式獲取一個網頁元素,如果沒有這種物件,則返回null

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

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

我們如何實現這一點呢?

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

let user = {};

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

這樣可以,這裡就不會出現錯誤了……但是不夠優雅。就像你所看到的,"user.address"在程式碼中出現了兩次。對於巢狀層次更深的屬性就會出現更多次這樣的重複,這就是問題了。

例如,讓我們嘗試獲取user.address.street.name

我們既需要檢查user.address,又需要檢查user.address.street

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

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

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

甚至我們可以先忽略這個問題,因為我們有一種更好的實現方式,就是使用&&運算子:

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

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

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

就像你所看到的,在程式碼中我們仍然重複寫了好幾遍物件屬性名。例如在上面的程式碼中,user.address被重複寫了三遍。

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

可選鏈

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

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

換句話說,例如value?.prop

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

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

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

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

程式碼簡潔明瞭,也不用重複寫好幾遍屬性名。

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

let user = null;

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

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

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

不要過度使用可選鏈

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

例如,如果根據我們的程式碼邏輯,user物件必須存在,但address是可選的,那麼我們應該這樣寫user.address?.street,而不是這樣user?.address?.street

所以,如果user恰巧因為失誤變為 undefined,我們會看到一個程式設計錯誤並修復它。否則,程式碼中的錯誤在不恰當的地方被消除了,這會導致除錯更加困難。

?.前的變數必須已宣告

如果未宣告變數user,那麼user?.anything會觸發一個錯誤:

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

?.前的變數必須已宣告(例如let/const/var user或作為一個函式引數)。可選鏈僅適用於已宣告的變數。

短路效應

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

所以,如果後面有任何函式呼叫或者副作用,它們均不會執行。

例如:

let user = null;
let x = 0;

user?.sayHi(x++); // 沒有 "sayHi",因此程式碼執行沒有觸達 x++

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

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

可選鏈?.不是一個運算子,而是一個特殊的語法結構。它還可以與函式和方括號一起使用。

例如,將?.()用於呼叫一個可能不存在的函式。

在下面這段程式碼中,有些使用者具有admin方法,而有些沒有:

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

let userGuest = {};

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

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

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

然後?.()會檢查它左邊的部分:如果admin函式存在,那麼就呼叫執行它(對於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
我們可以使用?.來安全地讀取或刪除,但不能寫入

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

例如:

let user = null;

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

這還不是那麼智慧。

總結

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

  1. obj?.prop—— 如果obj存在則返回obj.prop,否則返回undefined
  2. obj?.[prop]—— 如果obj存在則返回obj[prop],否則返回undefined
  3. obj.method?.()—— 如果obj.method存在則呼叫obj.method(),否則返回undefined

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

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

但是,我們應該謹慎地使用?.,僅在當左邊部分不存在也沒問題的情況下使用為宜。以保證在程式碼中有程式設計上的錯誤出現時,也不會對我們隱藏。