最近新增的特性
這是壹個最近添加到 JavaScript 的特性。 舊式浏覽器可能需要 polyfills.
可選鏈 ?.
是壹種訪問嵌套對象屬性的安全的方式。即使中間的屬性不存在,也不會出現錯誤。
如果妳才剛開始讀此教程並學習 JavaScript,那可能還沒接觸到這個問題,但它卻相當常見。
舉個例子,假設我們有很多個 user
對象,其中存儲了我們的用戶數據。
我們大多數用戶的地址都存儲在 user.address
中,街道地址存儲在 user.address.street
中,但有些用戶沒有提供這些信息。
在這種情況下,當我們嘗試獲取 user.address.street
,而該用戶恰好沒提供地址信息,我們則會收到壹個錯誤:
let user = {}; // 壹個沒有 "address" 屬性的 user 對象 alert(user.address.street); // Error!
這是預期的結果。JavaScript 的工作原理就是這樣的。因爲 user.address
爲 undefined
,嘗試讀取 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"
在代碼中出現了兩次。
我們看壹個以相同方式獲取 document.querySelector
的例子:
let html = document.querySelector('.elem') ? document.querySelector('.elem').innerHTML : null;
我們可以看到用于進行元素搜索的 document.querySelector('.elem')
在這裏實際上被調用了兩次。這樣不優雅。
對于嵌套層次更深的屬性,代碼會變得更醜,因爲需要更多的重複。
例如,讓我們以相同的方式嘗試獲取 user.address.street.name
。
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
相同,
否則(當 value
爲 undefined/null
時)則返回 undefined
。
下面這是壹種使用 ?.
安全地訪問 user.address.street
的方式:
let user = {}; // user 沒有 address 屬性 alert( user?.address?.street ); // undefined(不報錯)
代碼簡潔明了,也不用重複寫好幾遍屬性名。
這裏是壹個結合 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
。更深層次的屬性是通過常規方式訪問的。如果我們希望它們中的壹些也是可選的,那麽我們需要使用更多的 ?.
來替換 .
。
不要過度使用可選鏈
我們應該只將 ?.
使用在壹些東西可以不存在的地方。
例如,如果根據我們的代碼邏輯,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++); // 沒有 "user",因此代碼執行沒有到達 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
屬性,因爲我們假定對象 userAdmin
存在,因此可以安全地讀取它。
然後 ?.()
會檢查它左邊的部分:如果 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"
可選鏈 ?.
語法有三種形式:
obj?.prop
—— 如果 obj
存在則返回 obj.prop
,否則返回 undefined
。
obj?.[prop]
—— 如果 obj
存在則返回 obj[prop]
,否則返回 undefined
。
obj.method?.()
—— 如果 obj.method
存在則調用 obj.method()
,否則返回 undefined
。
正如我們所看到的,這些語法形式用起來都很簡單直接。?.
檢查左邊部分是否爲 null/undefined
,如果不是則繼續運算。
?.
鏈使我們能夠安全地訪問嵌套屬性。
但是,我們應該謹慎地使用 ?.
,根據我們的代碼邏輯,僅在當左側部分不存在也可接受的情況下使用爲宜。以保證在代碼中有編程上的錯誤出現時,也不會對我們隱藏。