[ES6深度解析]7:符號(Symbols)
第七種型別
自從JavaScript在1997年首次標準化以來,已經有了六種型別。在ES6之前,JS程式中的每個值都屬於這些類別之一:
- Undefined
- Null
- Boolean
- Number
- String
- Object
每種型別都是一組值。前五個集合都是有限的。當然,只有兩個布林值,true
和false
,而且它們不會產生新的值。有更多的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-in
或Object.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
如果覺得文章不錯,歡迎點選推薦