1. 程式人生 > 其它 >[ES6深度解析]7:符號(Symbols)

[ES6深度解析]7:符號(Symbols)

第七種型別

自從JavaScript在1997年首次標準化以來,已經有了六種型別。在ES6之前,JS程式中的每個值都屬於這些類別之一:

  • Undefined
  • Null
  • Boolean
  • Number
  • String
  • Object
    每種型別都是一組值。前五個集合都是有限的。當然,只有兩個布林值,truefalse,而且它們不會產生新的值。有更多的Number和String值。該標準稱,共有18,437,736,874,454,810,627個不同的數字(包括NaN,即非數字的縮寫)。與可能的字串的數量相比,這簡直是九牛一毛。

然而,Object值的集合是開放式的。每一件物品都是獨一無二的、珍貴的雪花。每次開啟Web頁面時,都會建立大量新物件。

ES6 Symbols是值,但不是字串。他們不是物件。它們是新的東西:第七種型別的值。讓我們來談談它們可能會派上用場的情況。

一個簡單的布林值

有時,將一些額外的資料儲存在真正屬於其他人的JavaScript物件上是非常方便的。例如,假設您正在編寫一個JS庫,它使用CSS轉換使DOM元素在螢幕上快速移動。你已經注意到,嘗試在單個div上同時應用多個CSS過渡是行不通的。它會導致醜陋的、不連續的“跳躍”。你認為可以修復這個問題,但首先你需要一種方法來確定給定元素是否已經在移動。

這個問題該如何解決?

一種方法是使用CSS APIs詢問瀏覽器元素是否在移動。但這聽起來有點過分了。你的庫應該已經知道元素在移動;這是一開始讓它移動的程式碼!你真正需要的是一種跟蹤哪些元素在移動的方法。你可以儲存一個包含所有移動元素的陣列。每次呼叫庫動畫元素時,都可以搜尋陣列,檢視該元素是否已經存在。但是如果陣列很大,線性搜尋會很慢。

另一個辦法是在元素上設定一個標誌:

if (element.isMoving) {
  smoothAnimations(element);
}
element.isMoving = true;

這也存在一些潛在的問題。它們都與這樣一個事實有關:你的程式碼並不是唯一使用DOM的程式碼。

  • 其他使用for-inObject.keys()的程式碼可能會在你建立的屬性上出錯。
  • 其他一些聰明的庫作者可能首先想到了這種技術,因此你的庫與現有庫的互動會很糟糕。(屬性名重複了,會有衝突)
  • 其他一些聰明的庫作者可能會在未來想到它,而你的js庫與那個未來的庫進行糟糕的互動。
  • 標準委員會可能決定向所有元素新增.ismoving()
    方法。那麼你真的傻眼了。

當然,你可以通過選擇一個非常乏味或愚蠢的字串來解決最後三個問題:(避免重名)

if (element.__$jorendorff_animation_library$PLEASE_DO_NOT_USE_THIS_PROPERTY$isMoving__) {
  smoothAnimations(element);
}
element.__$jorendorff_animation_library$PLEASE_DO_NOT_USE_THIS_PROPERTY$isMoving__ = true;

這程式碼辣眼睛!

你還可以使用加密技術為屬性生成一個實際唯一的名稱:

// get 1024 Unicode characters of gibberish
var isMoving = SecureRandom.generateName();

...

if (element[isMoving]) {
  smoothAnimations(element);
}
element[isMoving] = true;

Object[name]語法允許使用任意字串作為屬性名。所以這是可行的:命名衝突實際上是不可能的,你的程式碼看起來是正常的。但這將導致糟糕的除錯體驗。每當你在console.log()中新增一個帶有該屬性的元素時,將看到一個巨大的垃圾字串。如果你需要不止一個這樣的屬性呢?你是如何讓它們保持一致的?每次重新載入時,它們都會有不同的名稱。

為什麼這麼難?我們只需要一個布林值!

Symbols就是你要的答案

Symbols是程式可以建立並用作屬性鍵的值,而不會有名稱衝突的風險

var mySymbol = Symbol();

呼叫Symbol()將建立一個新的符號,該符號的值不等於任何其他值。

就像字串或數字一樣,可以使用符號作為屬性鍵。因為它不等於任何字串,所以這個符號鍵控屬性保證不會與任何其他屬性發生衝突。

obj[mySymbol] = "ok!";  // mySymbol是不會重複的屬性名
console.log(obj[mySymbol]);  // ok!

以下是在上面討論的情況下如何使用符號:

// create a unique symbol
var isMoving = Symbol("isMoving");

...

if (element[isMoving]) {
  smoothAnimations(element);
}
element[isMoving] = true;

關於這段程式碼的幾點注意事項:

  • Symbol("isMoving")中的字串"isMoving"被稱為描述。這對除錯很有幫助。當你將symbol寫入console.log()時,當使用.tostring()將其轉換為字串時,以及可能在錯誤訊息中都會顯示它。這就是描述的用途。
  • element[isMoving]被稱為符號鍵控屬性(symbol-keyed property)。它只是一個名稱是符號而不是字串的屬性。除此之外,它在任何方面都是正常的性質。
  • 與陣列元素一樣,符號鍵控屬性不能使用點語法訪問,如obj.name。必須使用方括號訪問它們。
  • 如果已經獲得了符號鍵控屬性,那麼訪問該符號鍵控屬性是很簡單的。上面的例子展示瞭如何獲取和設定element[isMoving],我們還可以詢問if (isMoving in element),甚至如果需要的話可以刪除delete element[isMoving]
  • 另一方面,只要isMoving在作用域內,所有這些都是可能的。這使得Symbol成為一種弱封裝機制:為自己建立一些Symbol的模組可以在任何它想要的物件上使用它們,而不必擔心與其他程式碼建立的屬性衝突

因為符號鍵(symbol keys)是為了避免衝突而設計的,所以JavaScript最常見的物件檢查特性就是簡單地忽略符號鍵。例如,for-in迴圈只在物件的字串鍵上迴圈。跳過符號鍵。Object.keys(obj)Object.getOwnPropertyNames(obj)做同樣的事情。但是Symbol並不是完全私有的:可以使用新的APIObject. getownpropertysymbols(obj)來列出物件的符號鍵。另一個新的APIReflect.ownKeys(obj)同時返回字串和符號鍵。

但到底什麼是符號Symbols呢?

> typeof Symbol()
"symbol"

Symbols和其他東西不完全一樣。

它們一旦被創造就不可改變。你不能在它們上設定屬性(如果你在嚴格模式下嘗試,你會得到一個TypeError)。它們可以是屬性名。這些都是類似String的性質。

另一方面,每個Symbol都是獨一無二的,不同於所有其他符號(甚至其他具有相同描述的符號),你可以輕鬆建立新的符號。這些都是類似Object的特性。

ES6 Symbol類似於Lisp和Ruby等語言中更傳統的符號,但並沒有緊密地整合到語言中。在Lisp中,所有識別符號都是Symbols。在JS中,識別符號和大多數屬性鍵仍然被認為是字串。Symbols只是一個額外的選擇。

關於Symbol的一個快速警告:不像語言中的其他任何東西,它們不能自動轉換為字串。試圖將符號與字串進行轉換將導致TypeError。

> var sym = Symbol("<3");
> "your symbol is " + sym
// TypeError: can't convert symbol to string
> `your symbol is ${sym}`
// TypeError: can't convert symbol to string

可以通過顯式地將符號轉換為字串來避免這種情況,例如寫入String(sym)sym.tostring()

三組符號

有三種方法可以獲得一個Symbol:

  • 呼叫Symbol()。正如我們已經討論過的,每次呼叫它都會返回一個新的唯一符號。
  • 呼叫Symbol.for(string)。這將訪問一組稱為符號登錄檔(symbol registry)的現有符號(symbol)。與Symbol()定義的唯一符號不同,符號登錄檔中的符號是共享的。如果你呼叫Symbol.for("cat") 30次,它每次都會返回相同的符號。當多個網頁或同一網頁中的多個模組需要共享一個符號時,登錄檔是有用的。
  • 使用由標準定義的符號,比如Symbol.iterator。一些符號是由標準本身定義的。每一個都有它自己的特殊目的。

Symbol的應用

Symbol.iterator

我們已經看到了ES6使用符號來避免與現有程式碼衝突的一種方法。在關於迭代器的文章中,我們看到for (var item of myArray)的迴圈首先呼叫myArray[Symbol.iterator]()。這個方法可以被稱為myArray.iterator(),但是symbol符號更利於程式碼向後相容。

讓instanceof可擴充套件

在ES6中,表示式 object instanceof constructor被指定為建構函式constructor的一個方法:constructor[Symbol.hasInstance](object)。這意味著它是可擴充套件的。

消除新特性和舊程式碼之間的衝突

某些ES6 Array方法僅僅出現在程式碼裡就破壞了現有的網站。其他Web標準也有類似的問題:簡單地在瀏覽器中新增新方法就會破壞現有的站點。然而,這種破壞主要是由所謂的動態作用域(dynamic scope)造成的,所以ES6引入了一種特殊的符號symbol.unscopables, Web標準可以使用它來防止某些方法捲入動態作用域。

支援新的字串匹配方式

在ES5中,str.match(myObject)會試圖把myObject轉換為RegExp(正則表示式)。在ES6中,JS首先會檢查myObject是否有一個myObject[Symbol.match](str)方法。現在JS庫可以提供自定義字串解析類,這些類可以在RegExp物件工作的所有地方工作。

每一種用途都很小眾。這些特性本身很難對我的日常程式碼產生重大影響。長遠的觀點更有趣。眾所周知的符號是JavaScript在PHP和Python中__doubleUnderscores下劃線的改進版本。該標準將來將使用它們向語言中新增新的鉤子,而不會對現有程式碼造成風險。

本文來自部落格園,作者:Max力出奇跡,轉載請註明原文連結:https://www.cnblogs.com/welody/p/15176871.html

如果覺得文章不錯,歡迎點選推薦