您好,登錄后才能下訂單哦!
Symbol 類型
根據規范,對象的屬性鍵只能是字符串類型或者 Symbol 類型。不是 Number,也不是 Boolean,只有字符串或 Symbol 這兩種類型。
到目前為止,我們只見過字符串。現在我們來看看 Symbol 能給我們帶來什么好處。
Symbol
"Symbol" 值表示唯一的標識符。
可以使用 Symbol() 來創建這種類型的值:
// id 是 symbol 的一個實例化對象 let id = Symbol();
創建時,我們可以給 Symbol 一個描述(也稱為 Symbol 名),這在代碼調試時非常有用:
// id 是描述為 "id" 的 Symbol let id = Symbol("id");
Symbol 保證是唯一的。即使我們創建了許多具有相同描述的 Symbol,它們的值也是不同。描述只是一個標簽,不影響任何東西。
例如,這里有兩個描述相同的 Symbol —— 它們不相等:
let id1 = Symbol("id"); let id2 = Symbol("id"); alert(id1 == id2); // false
如果你熟悉 Ruby 或者其他有 "Symbol" 的語言 —— 別被誤導。JavaScript 的 Symbol 是不同的。
注意:Symbol 不會被自動轉換為字符串
JavaScript 中的大多數值都支持字符串的隱式轉換。例如,我們可以 alert 任何值,都可以生效。Symbol 比較特殊,它不會被自動轉換。
例如,這個 alert 將會提示出錯:
let id = Symbol("id"); alert(id); // 類型錯誤:無法將 Symbol 值轉換為字符串。
這是一種防止混亂的“語言保護”,因為字符串和 Symbol 有本質上的不同,不應該意外地將它們轉換成另一個。
如果我們真的想顯示一個 Symbol,我們需要在它上面調用 .toString(),如下所示:
let id = Symbol("id"); alert(id.toString()); // Symbol(id),現在它有效了
或者獲取 symbol.description 屬性,只顯示描述(description):
let id = Symbol("id"); alert(id.description); // id
“隱藏”屬性
Symbol 允許我們創建對象的“隱藏”屬性,代碼的任何其他部分都不能意外訪問或重寫這些屬性。
例如,如果我們使用的是屬于第三方代碼的 user 對象,我們想要給它們添加一些標識符。
我們可以給它們使用 Symbol 鍵:
let user = { // 屬于另一個代碼 name: "John" }; let id = Symbol("id"); user[id] = 1; alert( user[id] ); // 我們可以使用 Symbol 作為鍵來訪問數據
在字符串 "id" 上使用 Symbol("id") 有什么好處?
因為 user 屬于另一個代碼,另一個代碼也使用它執行一些操作,所以我們不應該在它上面加任何字段,這樣很不安全。但是 Symbol 不會被意外訪問到,所以第三方代碼看不到它,所以使用 Symbol 也許不會有什么問題。
另外,假設另一個腳本希望在 user 中有自己的標識符,以實現自己的目的。這可能是另一個 JavaScript 庫,因此腳本之間完全不了解彼此。
然后該腳本可以創建自己的 Symbol("id"),像這樣:
// ... let id = Symbol("id"); user[id] = "Their id value";
我們的標識符和他們的標識符之間不會有沖突,因為 Symbol 總是不同的,即使它們有相同的名字。
……但如果我們處于同樣的目的,使用字符串 "id" 而不是用 symbol,那么 就會 出現沖突:
let user = { name: "John" }; // 我們的腳本使用了 "id" 屬性。 user.id = "Our id value"; // ……另一個腳本也想將 "id" 用于它的目的…… user.id = "Their id value" // 砰!無意中被另一個腳本重寫了 id!
字面量中的 Symbol
如果我們要在對象字面量 {...} 中使用 Symbol,則需要使用方括號把它括起來。
就像這樣:
let id = Symbol("id"); let user = { name: "John", [id]: 123 // 而不是 "id:123" };
這是因為我們需要變量 id 的值作為鍵,而不是字符串 "id"。
Symbol 在 for..in 中會被跳過
Symbolic 屬性不參與 for..in 循環。
例如:
let id = Symbol("id"); let user = { name: "John", age: 30, [id]: 123 }; for (let key in user) alert(key); // name, age (no symbols) // 使用 Symbol 任務直接訪問 alert( "Direct: " + user[id] );
Object.keys(user) 也會忽略它們。這是一般“隱藏符號屬性”原則的一部分。如果另一個腳本或庫遍歷我們的對象,它不會意外地訪問到符號屬性。
相反,Object.assign 會同時復制字符串和 symbol 屬性:
let id = Symbol("id"); let user = { [id]: 123 }; let clone = Object.assign({}, user); alert( clone[id] ); // 123
這里并不矛盾,就是這樣設計的。這里的想法是當我們克隆或者合并一個 object 時,通常希望 所有 屬性被復制(包括像 id 這樣的 Symbol)。
其他類型的屬性鍵被強制為字符串:
我們只能在對象中使用字符串或 symbol 作為鍵,其它類型會被轉換為字符串。
例如,在作為屬性鍵使用時,數字 0 變成了字符串 "0":
let obj = { 0: "test" // 和 "0": "test" 一樣 }; // 兩個 alert 都訪問相同的屬性(Number 0 被轉換為字符串 "0") alert( obj["0"] ); // test alert( obj[0] ); // test(同一個屬性)
全局 symbol
正如我們所看到的,通常所有的 Symbol 都是不同的,即使它們有相同的名字。但有時我們想要名字相同的 Symbol 具有相同的實體。例如,應用程序的不同部分想要訪問的 Symbol "id" 指的是完全相同的屬性。
為了實現這一點,這里有一個 全局 Symbol 注冊表。我們可以在其中創建 Symbol 并在稍后訪問它們,它可以確保每次訪問相同名字的 Symbol 時,返回的都是相同的 Symbol。
要從注冊表中讀取(不存在則創建)Symbol,請使用 Symbol.for(key)。
該調用會檢查全局注冊表,如果有一個描述為 key 的 Symbol,則返回該 Symbol,否則將創建一個新 Symbol(Symbol(key)),并通過給定的 key 將其存儲在注冊表中。
例如:
// 從全局注冊表中讀取 let id = Symbol.for("id"); // 如果該 Symbol 不存在,則創建它 // 再次讀取(可能是在代碼中的另一個位置) let idAgain = Symbol.for("id"); // 相同的 Symbol alert( id === idAgain ); // true
注冊表內的 Symbol 被稱為 全局 Symbol。如果我們想要一個應用程序范圍內的 Symbol,可以在代碼中隨處訪問 —— 這就是它們的用途。
這聽起來像 Ruby:
在一些編程語言中,例如 Ruby,每個名字都有一個 Symbol。
正如我們所看到的,在 JavaScript 中,全局 Symbol 也是這樣的。
Symbol.keyFor
對于全局 Symbol,不僅有 Symbol.for(key) 按名字返回一個 Symbol,還有一個反向調用:Symbol.keyFor(sym),它的作用完全反過來:通過全局 Symbol 返回一個名字。
例如:
// 通過 name 獲取 Symbol let sym = Symbol.for("name"); let sym2 = Symbol.for("id"); // 通過 Symbol 獲取 name alert( Symbol.keyFor(sym) ); // name alert( Symbol.keyFor(sym2) ); // id
Symbol.keyFor 內部使用全局 Symbol 注冊表來查找 Symbol 的鍵。所以它不適用于非全局 Symbol。如果 Symbol 不是全局的,它將無法找到它并返回 undefined。
也就是說,任何 Symbol 都具有 description 屬性。
例如:
let globalSymbol = Symbol.for("name"); let localSymbol = Symbol("name"); alert( Symbol.keyFor(globalSymbol) ); // name,全局 Symbol alert( Symbol.keyFor(localSymbol) ); // undefined,非全局 alert( localSymbol.description ); // name
系統 Symbol
JavaScript 內部有很多“系統” Symbol,我們可以使用它們來微調對象的各個方面。
它們都被列在了眾所周知的 Symbol 表的規范中:
例如,Symbol.toPrimitive 允許我們將對象描述為原始值轉換。我們很快就會看到它的使用。
當我們研究相應的語言特征時,我們對其他的 Symbol 也會慢慢熟悉起來。
總結
Symbol 是唯一標識符的基本類型
Symbol 是使用帶有可選描述(name)的 Symbol() 調用創建的。
Symbol 總是不同的值,即使它們有相同的名字。如果我們希望同名的 Symbol 相等,那么我們應該使用全局注冊表:Symbol.for(key) 返回(如果需要的話則創建)一個以 key 作為名字的全局 Symbol。使用 Symbol.for 多次調用 key 相同的 Symbol 時,返回的就是同一個 Symbol。
Symbol 有兩個主要的使用場景:
因此我們可以使用 Symbol 屬性“秘密地”將一些東西隱藏到我們需要的對象中,但其他地方看不到它。
從技術上說,Symbol 不是 100% 隱藏的。有一個內置方法Object.getOwnPropertySymbols(obj) 允許我們獲取所有的 Symbol。還有一個名為 Reflect.ownKeys(obj) 的方法可以返回一個對象的 所有 鍵,包括 Symbol。所以它們并不是真正的隱藏。但是大多數庫、內置方法和語法結構都沒有使用這些方法。
以上就是本文的全部內容,希望對大家的學習有所幫助,也希望大家多多支持億速云。
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。