【探祕ES6】系列專欄(八):JS的第七種基本類型Symbols

ES6做爲新一代JavaScript標準,已正式與廣大前端開發者見面。爲了讓你們對ES6的諸多新特性有更深刻的瞭解,Mozilla Web開發者博客推出了《ES6 In Depth》系列文章。CSDN已獲受權,將持續對該系列進行翻譯,組織成【探祕ES6】系列專欄,供你們學習借鑑。本文爲該系列的第八篇。 
前端

本期咱們要討論的symbols是個什麼東西呢?es6

這裏的Symbols不是指的徽標。正則表達式

也不是能在代碼中使用的小圖片。算法


它也不是表明其它任何東西的一個別名。編程

固然咯,Symbols和Cymbals(銅鈸)徹底是兩回事。數組


(在編程過程當中使用銅鈸可不是一個好主意,吵到你炸!)瀏覽器

言歸正傳,什麼是Symbols呢?框架

它是Javascript的第七種基本類型dom

自1997年Javascript被標準化以來,它定義了六種基本類型。直到ES6,JS程序中任何一個值都屬於如下幾種類型之一。ide

  • Undefined

  • Null

  • Boolean

  • Number

  • String

  • Object

每種類型都是一系列值的集。前五個都是有限集。固然,Boolean類型只有true和false兩個值,並且他們應該不會給Boolean型增長新值了。其它類型的值基本上都是數字和字符串。理論上說Numbers類型有18,437,736,874,454,810,627個值(包括了NaN,NaN是「Not a Number」的縮寫)。String類型中可能的值就太多了,我算算大概有 (2144,115,188,075,855,872 − 1) ÷ 65,535個……固然,我這種算法不必定是精確的。

Object是一個無限集,每個Object都是獨一無二的。你隨意打開一個Web頁面就會生成一大堆新的Object。

ES6 Symbols也是一個集,但它的元素既不是字符串也不是對象。它是ES6的新成員:第七種基本類型。

讓咱們來談談它的應用場景。

以一個簡單的布爾型來舉例

在JavaScript中,有時候將一個對象中的數據擴展到其它某個對象中是十分方便的。

例如,假設你正在寫一個JS庫,目的是使用CSS過渡讓DOM元素在屏幕上移動。你應該知道同時使用多個CSS過渡在同一個div上是行不通的。這會引發div不規律跳躍。你打算解決這個問題,不過首先你得想法知道這個元素是否正處在一個過渡中。

怎樣來解決這個問題呢?

其中一種方式是使用CSS APIs讓瀏覽器來告訴你元素是否在位移過程當中。但這未免有點殺雞用牛刀了。你的庫應該存儲了移動狀態:代碼中觸發過渡的時候就應該記錄了!

你真正須要的是一種方法來跟蹤記錄哪些元素在過渡。你能夠把過渡中的元素存在一個數組中。每當你的庫觸發一個元素的過渡以前,先檢測那個元素是否在數組中。

遺憾的是,若是數組很大的話,遍歷起來會很耗時。

在你看來最簡單的方法實際上是爲元素設置一個標識:

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

這樣也會有一些潛在的問題。沒法避免的事實是代碼中會用到這個DOM的地方不止這一處。
  1. 其它代碼中若是使用了for-in 或者 Object.keys()會遍歷DOM的全部屬性(會形成額外性能消耗)。

  2. 一些思惟靈活的庫做者會從技術方面考慮——你的庫與其它庫兼容性會不好。

  3. 一些思惟靈活的庫做者也會考慮擴展性——你的庫擴展性也會不好。

  4. 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()來建立一個新的Symbol值,它不會等同於其餘值。

與字符串和數字同樣,你可使用symbol來做爲屬性值。由於它不等同於其它任何字符串,這個symbol-keyed屬性能夠確保不會與其它任何屬性衝突。

obj[mySymbol] = "ok!";  // guaranteed not to collide  
console.log(obj[mySymbol]);  // ok!

接下來這方法就能夠解決上面咱們所討論的那種狀況:
// create a unique symbol  
var isMoving = Symbol("isMoving");  
...  
if (element[isMoving]) {  
  smoothAnimations(element);  
}  
element[isMoving] = true;

關於這段代碼的幾個說明:
  • Symbol(「isMoving」)中的「isMoving」被稱做描述。它對調試頗有用。當你使用console.log()就能夠打印出對應的symbol值,若是你想把它轉換爲字符串(好比說在打印錯誤信息的時候)可使用.toString()。

  • element[isMoving]被稱做symbol-keyed屬性(使用symbol做爲鍵的屬性)。從字面意思就能夠說明它就是使用symbol做爲屬性名而不是使用字符串。除去這一點,它和其它屬性並沒什麼區別。

  • 和數組元素同樣,symbol-keyed屬性不能經過圓點符號來獲取值(obj.name 這樣是不行的)。它的值必須經過方括號來獲取。

  • 經過symbol的值獲取symbol-keyed屬性值就很容易了。上面的例子展現瞭如何獲取和設置element[isMoving],咱們能夠判斷元素的isMoving狀態了,若是有必要的話甚至能夠刪除isMoving狀態。

  • 另外一方面,以上的前提是isMoving在當前做用域中。這體現了symbols的弱封裝機制:一個模塊能夠建立幾個symbols在對象中任意使用而不用擔憂與其它模塊的屬性衝突。

由於symbol鍵值是被設計來避免衝突的,因此JavaScript最基本的對象檢測特性是會忽略symbol鍵值的。以for-in循環爲例,循環只會遍歷對象的字符串類型的鍵。Symbol鍵直接被忽略過了。Object.key(obj)和 Object.getOwnPropertyNames(obj) 也是這樣運做的。可是sysmbols並不徹底是私有的:可使用新API——Object.getOwnPropertySymbols(obj)將所對象的全部symbol鍵;另外一個新API—— Reflect.ownKeys(obj),將會同時返回string和symbol類型的鍵。(在之後的文章中咱們將完整地探討Reflect API。)

在庫和框架中symbols將會有不少用途,不久咱們會看到,JS語言自己對它也會有普遍的使用。

symbols確切定義是什麼呢?

<b>> typeof Symbol()  
"symbol"</b>

Symbols和其它基本類型大不同。

從建立開始就是不可變的。你不能爲它設置屬性(若是你在嚴謹模式下嘗試,會報類型錯誤)。它能夠做爲屬性名。這是它的類字符串性質。

另外一方面,每個symbol都是惟一的。與其餘的不一樣(就算他們的描述是同樣的)你能夠很容易地新建立一個。這是它的類對象性質。

ES6 symbols與Lisp和Ruby中的更傳統的symbols很相似,可是沒有如此緊密地集成到語言中。在Lisp中,全部的標識符都是symbols。在JS中,標識符和大多數屬性的鍵值的首先還是字符串,Symbols只是爲開發人員提供了一個額外選擇。

關於symbols的一個忠告:與JS中的其它類型不一樣,它不能被自動轉換爲字符串。試圖拼接symbol與字符串將會引發類型錯誤。

> 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

你能夠經過顯示地將symbol轉換爲一個字符串來避免這個問題,經過String(sym)或者sym.toString()。

symbols的三種形式

有三種方法來獲取symbol。

  • Call Symbol()。咱們已經討論過這種方法了,每一次調用它都將返回一個惟一的symbol。

  • Call Symbol.for(string)。這種方法訪問一組已經存在的symbol註冊表。與經過Symbol()來定一個惟一值不一樣的是,symbol註冊表中的symbols是共享的。若是你調用Symbol.for(「cat」)三十次,每一次返回都將是同一個symbol。在多頁面或者單頁面的多模塊須要共享symbol時,這是頗有效的方法。

  • 使用標準中定義的Symbol.iterator。標準委員會本身定義了幾種symbols。每一種都有它的特殊意義。

若是你仍然不肯定symbols是否對你有幫助,這最後一個章節會頗有趣,由於證明了在實踐中symbols是頗有用的。

ES6的文檔中對通用symbols的使用是如何介紹的?

咱們已經看過了ES6是如何使用symbol來避免與已有代碼命名衝突的。幾周前,在關於迭代器的文章中,咱們瞭解了循環(var item of myArray)是從調用myArray[Symbol.iterator]()開始的。我提到這個方法之前的寫法是myArray.iterator(),可是加了symbol之後向後兼容性會更好。

如今咱們知道了symbols的用法和做用。那麼就很容易理解爲何這樣作和這樣作的意義是什麼。

這裏還有其它幾個ES6使用通用symbols的場景。(這些特性在Firefox中還沒實現。)

  • 使instanceof可擴展。在ES6中,表達式object instanceof constructor被指定爲構造函數的一個方法:constructor[Symbol.hasInstance](Object)。這代表它是可擴展的。

  • 消除新特性和舊代碼之間的衝突。這比較難理解,但咱們發現一些ES6的數組方法將會破壞舊網站的穩定性。其它的Web標準也會有相似的問題:僅僅是添加新方法到瀏覽器中,已存在的網站就會受到影響。不管如何,形成這些不穩定性的主要緣由主要是由動態做用域引發的。因此ES6引入了一個特殊的symbol——Symbol.unscopables,這個Web標準能夠用來防止某些方法被牽連到動態域中。

  • 支持新的字符串匹配。在ES5中,str.match(myObject)嘗試將myObject轉換爲正則表達式對象。在ES6中,首先檢查myObject是否有myObject[Symbol.match](str)方法。如今庫就能夠給任何有正則表達式對象的地方提供通用的解析類。

所講到的這幾個symbol的應用都不常見,很難看到這些特性自己對咱們的平常代碼有任何影響。從長遠看就比較有意義了。通用symbols是JavaScript對於PHP和Python中的__doubleUnderscores的改進。標準委員會未來會添加新的hooks到JS中,而不會有影響已有代碼的風險。

我何時能夠開始使用ES6 symbols?

Firefox 36和Chrome 38已經支持Symbols了。我本身也在Firefox中試過了,若是你運行的時候有問題,你該知道問誰吧——找我!

爲了讓那些自己還不支持ES6 symbols的瀏覽器支持它,你可使用pollyfill(一段代碼或插件,提供了那些開發者們但願瀏覽器原生提供支持的功能),好比core.js。由於Symbols還比較新,因此它的pollyfill還不是那麼完善,詳細瞭解請看使用說明。

接下來的兩篇博客,首先會討論一些咱們期待已久的特性終於被ES6支持了,我實在忍不住抱怨它們的姍姍來遲。咱們將從兩個很古老的特性做爲開始(老到幾乎能夠追溯到編程歷史的起源),緊接着討論兩個與之很是類似的特性,由ephemerons提供技術支持。下次還將深刻討論collections(集合)。

原文連接:ES6 In Depth: Symbols

本譯文遵循Creative Commons Attribution Share-Alike License v3.0

相關文章
相關標籤/搜索