根據規範,對象的屬性鍵只能是 String 類型或者 Symbol 類型。不是 Number,也不是 Boolean,只有 String 或 Symbol 這兩種類型。git
到目前爲止,咱們只見過 String。如今咱們來看看 Symbol 能給咱們帶來什麼好處。github
"Symbol" 值表示惟一的標識符。編程
可使用 Symbol()
來建立這種類型的值:編程語言
// id 是 symbol 的一個實例化對象 let id = Symbol();
咱們能夠給 Symbol 一個描述(也稱爲 Symbol 名),這對於調試很是有用:設計
// id 是描述爲 "id" 的 Symbol let id = Symbol("id");
Symbol 保證是惟一的。即便咱們建立了許多具備相同描述的 Symbol,它們的值也是不一樣。描述只是一個不影響任何東西的標籤。調試
例如,這裏有兩個描述相同的 Symbol —— 它們不相等:code
let id1 = Symbol("id"); let id2 = Symbol("id"); *!* alert(id1 == id2); // false */!*
若是您熟悉 Ruby 或者其餘有 "Symbol" 的語言 —— 別被誤導。JavaScript 的 Symbol 不同凡響。對象
JavaScript 中的大多數值都支持 string 的隱式轉換。例如,咱們能夠 `alert` 任何值,這會起做用。Symbol 是特別的,它沒法自動轉換。 例如,這個 `alert` 將會顯示錯誤:
let id = Symbol("id");
!
alert(id); // 類型錯誤:沒法將 Symbol 值轉換爲 String。
/!教程
若是咱們真的想顯示一個 Symbol,咱們須要在它上面調用 `.toString()`,以下所示:
let id = Symbol("id");
!
alert(id.toString()); // Symbol(id),如今它起做用了
/!ip
這是一種防止混亂的「語言保護」,由於 String 和 Symbol 有本質上的不一樣,並且不該該偶爾將它們相互轉化。
Symbol 容許咱們建立對象的「隱藏」屬性,代碼的任何其餘部分都不能偶爾訪問或重寫這些屬性。
例如,若是咱們想存儲 object user
的「標識符」,咱們可使用 Symbol 做爲它的鍵:
let user = { name: "John" }; let id = Symbol("id"); user[id] = "ID Value"; alert( user[id] ); // 咱們可使用 Symbol 做爲鍵來訪問數據。
在 string "id"
上使用 Symbol("id")
有什麼好處?
咱們用更深刻一點的示例來講明這一點。
假設另外一個腳本但願 user
中有它本身的 "id" 屬性能夠操做。這多是另外一個 JavaScript 庫,因此這些腳本徹底不知道對方是誰。
而後該腳本能夠建立本身的 Symbol("id")
,以下所示:
// ... let id = Symbol("id"); user[id] = "Their id value";
不會衝突,由於 Symbol 老是不一樣的,即便它們有相同的名稱。
如今請注意,若是咱們使用 String "id"
而不是用 symbol,那麼就會出現衝突:
let user = { name: "John" }; //咱們的腳本使用 "id" 屬性。 user.id = "ID Value"; // ...若是以後另外一個腳本爲其目的使用 "id"... user.id = "Their id value" // 砰!無心中重寫了 id!他不是故意傷害同事的,而是這樣作了!
若是咱們要在 object 字面量中使用 Symbol,則須要方括號。
就像這樣:
let id = Symbol("id"); let user = { name: "John", *!* [id]: 123 // 不只僅是 "id:123" */!* };
這是由於咱們須要變量 id
的值做爲鍵,而不是 String "id"。
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] );
這是通常「隱藏」概念的一部分。若是另外一個腳本或庫在咱們的對象上循環,它不會訪問一個 Symbol 類型的屬性。
相反,Object.assign 同時複製字符串和符號屬性:
let id = Symbol("id"); let user = { [id]: 123 }; let clone = Object.assign({}, user); alert( clone[id] ); // 123
這裏並不矛盾,就是這樣設計的。咱們的想法是當咱們克隆一個 object 或合併 object 時,一般但願全部屬性被複制(包括像 id
這樣的 Symbol)。
咱們只能在對象中使用 string 或 symbol 做爲鍵,其它類型轉換爲 String。 例如,在做爲屬性鍵使用時,數字 `0`變成了字符串 `"0"`:
let obj = {
0: "test" // same as "0": "test"
};
//兩個 alert 都訪問相同的屬性(Number 0 被轉換爲 String "0")
alert( obj["0"] ); // test
alert( obj[0] ); // test (相同屬性)
正如咱們所看到的,一般全部的 Symbol 都是不一樣的,即便它們有相同的名字。但有時咱們想要同一個名字的 Symbol 是相同的實體。
好比,咱們但願在應用程序的不一樣部分訪問相同的 Symbol "id"
屬性。
爲此,存在一個全局 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,每一個名稱都有一個 symbol。 在 JavaScript 中,咱們應該用全局 symbol。
對於全局 symbol,Symbol.for(key)
不只按名稱返回一個 symbol,並且還有一個反向調用:Symbol.keyFor(sym)
,反過來:經過全局 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
。
例如:
alert( Symbol.keyFor(Symbol.for("name")) ); // name, 全局 Symbol alert( Symbol.keyFor(Symbol("name2")) ); // undefined, 參數不是一個全局 Symbol
JavaScript 內部存在不少「系統」 Symbol,咱們可使用它們來微調對象的各個方面。
它們列在熟悉的 Symbol 表的規範中:
Symbol.hasInstance
Symbol.isConcatSpreadable
Symbol.iterator
Symbol.toPrimitive
例如,Symbol.toPrimitive
容許咱們將對象描述爲原始值轉換,咱們很快就會看到它的使用。
當咱們研究相應的語言特徵時,其餘 Symbol 也會變得熟悉起來。
Symbol
是惟一標識符的基本類型
Symbol 使用 Symbol()
建立的,調用帶有一個可選的描述。
Symbol 老是不一樣的值,即便它們有相同的名稱。若是咱們但願同名 Symbol 相等,那麼咱們應該使用全局註冊表:Symbol.for(key)
返回(若是須要的話建立)一個以 key
做爲名稱的全局 Symbol。Symbol.for
的屢次調用徹底返回相同的 Symbol。
Symbol 有兩個主要的使用場景:
for..in
中,所以不會無意列出。另外,它不會被直接訪問,由於另外一個腳本沒有咱們的符號,因此它不會不當心干預它的操做。所以咱們可使用 Symbol 屬性「祕密地」將一些東西隱藏到咱們須要的對象中,但其餘人不會以對象屬性的形式看到它。
Symbol.*
訪問。咱們可使用它們來改變一些內置行爲。例如,在本教程的後面部分,咱們將使用 Symbol.iterator
來迭代,Symbol.toPrimitive
來設置對象原始值的轉換等等。從技術上說,Symbol 不是 100% 隱藏的。有一個內置方法 Object.getOwnPropertySymbols(obj) 容許咱們獲取全部的 Symbol。還有一個名爲 Reflect.ownKeys(obj) 返回全部鍵,包括 Symbol。因此它們不是真正的隱藏。可是大多數庫、內置方法和語法結構都遵循一個共同的協議。而明確調用上述方法的人可能很清楚他在作什麼。