可選鏈接 ?.
是一種安全的方式來存取巢狀物件屬性,即使中間屬性不存在。
「不存在的屬性」問題
如果您剛開始閱讀教學課程並學習 JavaScript,也許您還沒有遇到這個問題,但它很常見。
舉例來說,假設我們有 user
物件,其中包含了我們的使用者資訊。
我們的大部分使用者都有位址,儲存在 user.address
屬性中,其中包含了街道 user.address.street
,但有些使用者沒有提供位址。
在這種情況下,當我們嘗試取得 user.address.street
,而使用者恰好沒有提供位址時,我們會得到一個錯誤
let user = {}; // a user without "address" property
alert(user.address.street); // Error!
這是預期的結果。JavaScript 就是這樣運作的。由於 user.address
是 undefined
,因此嘗試取得 user.address.street
會失敗並產生錯誤。
在許多實際情況中,我們會希望在此處取得 undefined
而不是錯誤(表示「沒有街道」)。
…另一個範例。在網頁開發中,我們可以使用特殊的方法呼叫來取得對應於網頁元素的物件,例如 document.querySelector('.elem')
,當沒有這樣的元素時,它會傳回 null
。
// document.querySelector('.elem') is null if there's no element
let html = document.querySelector('.elem').innerHTML; // error if it's 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 has no address
alert(user.address ? user.address.street ? user.address.street.name : null : null);
這很糟糕,甚至可能讓人難以理解這樣的程式碼。
有一種更好的寫法,使用 &&
運算子
let user = {}; // user has no address
alert( user.address && user.address.street && user.address.street.name ); // undefined (no error)
對整個屬性路徑進行 AND 運算,可以確保所有組件都存在(如果不存在,則評估會停止),但這也不是理想的。
正如你所見,屬性名稱仍然在程式碼中重複。例如,在上面的程式碼中,user.address
出現了三次。
這就是為什麼選擇性串接 ?.
被新增到語言中。一次解決這個問題!
選擇性串接
如果 ?.
前面的值是 undefined
或 null
,選擇性串接 ?.
會停止評估並傳回 undefined
。
為了簡潔起見,在本文的後續部分,如果某個東西不是 null
也不是 undefined
,我們會說它「存在」。
換句話說,value?.prop
- 如果
value
存在,則會像value.prop
一樣運作, - 否則(當
value
為undefined/null
)會傳回undefined
。
以下是使用 ?.
安全存取 user.address.street
的方法
let user = {}; // user has no address
alert( user?.address?.street ); // undefined (no error)
程式碼簡潔,而且完全沒有重複。
以下是用 document.querySelector
的範例
let html = document.querySelector('.elem')?.innerHTML; // will be undefined, if there's no element
即使 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
恰好是未定義的,我們會看到一個關於它的程式設計錯誤並修復它。否則,如果我們過度使用 ?.
,編碼錯誤可能會在不適當的地方被隱藏,並變得更難除錯。
?.
前面的變數必須宣告如果根本沒有 user
變數,則 user?.anything
會觸發錯誤
// ReferenceError: user is not defined
user?.address;
必須宣告變數(例如 let/const/var user
或作為函式參數)。可選鍊接僅適用於已宣告的變數。
短路
如前所述,如果左側部分不存在,?.
會立即停止(「短路」)評估。
因此,如果在 ?.
的右側有任何進一步的函式呼叫或運算,則不會執行這些呼叫或運算。
例如
let user = null;
let x = 0;
user?.sayHi(x++); // no "user", so the execution doesn't reach sayHi call and x++
alert(x); // 0, value not incremented
其他變體:?.()、?.[]
可選鍊接 ?.
不是運算子,而是一種特殊的語法結構,也可以用於函式和方括號。
例如,?.()
用於呼叫可能不存在的函式。
在以下程式碼中,我們的一些使用者有 admin
方法,而另一些使用者沒有
let userAdmin = {
admin() {
alert("I am admin");
}
};
let userGuest = {};
userAdmin.admin?.(); // I am admin
userGuest.admin?.(); // nothing happens (no such method)
在這裡,在兩行中,我們首先使用點(userAdmin.admin
)來取得 admin
屬性,因為我們假設 user
物件存在,因此可以安全地從中讀取。
然後 ?.()
檢查左側部分:如果 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; // delete user.name if user exists
?.
來安全地讀取和刪除,但不能寫入選擇性連鎖 ?.
在賦值運算的左側沒有作用。
例如
let user = null;
user?.name = "John"; // Error, doesn't work
// because it evaluates to: undefined = "John"
摘要
選擇性連鎖 ?.
語法有三個形式
obj?.prop
– 如果obj
存在,則傳回obj.prop
,否則傳回undefined
。obj?.[prop]
– 如果obj
存在,則傳回obj[prop]
,否則傳回undefined
。obj.method?.()
– 如果obj.method
存在,則呼叫obj.method()
,否則傳回undefined
。
正如我們所見,所有這些都非常直接且易於使用。?.
檢查左側是否存在 null/undefined
,如果不存在,則允許評估繼續進行。
?.
的鏈條允許安全地存取巢狀屬性。
儘管如此,我們應該小心地套用 ?.
,僅在根據我們的程式碼邏輯,左側不存在是可以接受的情況下才套用。這樣一來,如果發生程式設計錯誤,它就不會向我們隱藏這些錯誤。
留言
<code>
標籤,若要插入多行程式碼,請將它們包覆在<pre>
標籤中,若要插入超過 10 行的程式碼,請使用沙盒 (plnkr、jsbin、codepen…)