Symbol 類型

Symbol 類型

根據規範,對象的屬性鍵只能是 String 類型或者 Symbol 類型。不是 Number,也不是 Boolean,只有 String 或 Symbol 這兩種類型。git

到目前爲止,咱們只見過 String。如今咱們來看看 Symbol 能給咱們帶來什麼好處。github

Symbol

"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!他不是故意傷害同事的,而是這樣作了!

字面量中的 Symbol

若是咱們要在 object 字面量中使用 Symbol,則須要方括號。

就像這樣:

let id = Symbol("id");

let user = {
  name: "John",
*!*
  [id]: 123 // 不只僅是 "id:123"
*/!*
};

這是由於咱們須要變量 id 的值做爲鍵,而不是 String "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] );

這是通常「隱藏」概念的一部分。若是另外一個腳本或庫在咱們的對象上循環,它不會訪問一個 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 是相同的實體。

好比,咱們但願在應用程序的不一樣部分訪問相同的 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.keyFor

對於全局 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

系統 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 有兩個主要的使用場景:

  1. 「隱藏」 對象屬性。若是須要將屬性添加到 「屬於」 另外一個腳本或庫的對象中,則能夠建立 Symbol 並將其用做屬性鍵。Symbol 屬性不出如今 for..in中,所以不會無意列出。另外,它不會被直接訪問,由於另外一個腳本沒有咱們的符號,因此它不會不當心干預它的操做。

    所以咱們可使用 Symbol 屬性「祕密地」將一些東西隱藏到咱們須要的對象中,但其餘人不會以對象屬性的形式看到它。

  2. JavaScript 使用了許多系統 Symbol,這些 Symbol 能夠做爲 Symbol.* 訪問。咱們可使用它們來改變一些內置行爲。例如,在本教程的後面部分,咱們將使用 Symbol.iterator迭代Symbol.toPrimitive 來設置對象原始值的轉換等等。

從技術上說,Symbol 不是 100% 隱藏的。有一個內置方法 Object.getOwnPropertySymbols(obj) 容許咱們獲取全部的 Symbol。還有一個名爲 Reflect.ownKeys(obj) 返回全部鍵,包括 Symbol。因此它們不是真正的隱藏。可是大多數庫、內置方法和語法結構都遵循一個共同的協議。而明確調用上述方法的人可能很清楚他在作什麼。

相關文章
相關標籤/搜索