深入了解JavaScript中的Symbol的使用方法
Symbol 是什么?
Symbols 不是圖標(biāo),也不是指在代碼中可以使用小圖片:

也不是指代其他一些東西的語(yǔ)法。那么,Symbol 到究竟是什么呢?
七種數(shù)據(jù)類(lèi)型
JavaScript 在 1997 年被標(biāo)準(zhǔn)化時(shí),就有 6 種數(shù)據(jù)類(lèi)型,直到 ES6 出現(xiàn)之前,程序中的變量一定是以下 6 種數(shù)據(jù)類(lèi)型之一:
Undefined
Null
Boolean
Number
String
Object
每種數(shù)據(jù)類(lèi)型都是一系列值的組合,前面 5 種數(shù)據(jù)類(lèi)型值的數(shù)量都是有限的。Boolean 類(lèi)型只有兩個(gè)值:true 和 false,為 Boolean 類(lèi)型的變量賦值時(shí),并不會(huì)產(chǎn)生新的值(共享了true 和 false 這兩個(gè)值)。對(duì)于 Number 和 String 來(lái)說(shuō),它們的值則多得多了,標(biāo)準(zhǔn)的說(shuō)法是有 18,437,736,874,454,810,627 個(gè) Number 類(lèi)型的值(包括 NAN)。String 類(lèi)型的個(gè)數(shù)就難以統(tǒng)計(jì)了,我原以為是 (2144,115,188,075,855,872 ? 1) ÷ 65,535…不過(guò)也許我算錯(cuò)了。
對(duì)象值的個(gè)數(shù)是無(wú)限的,每個(gè)對(duì)象都是獨(dú)一無(wú)二的,每次打開(kāi)一個(gè)網(wǎng)頁(yè),都創(chuàng)建了一系列的對(duì)象。
ES6 中的 Symbol 也是一種數(shù)據(jù)類(lèi)型,但是不是字符串,也不是對(duì)象,而是一種新的數(shù)據(jù)類(lèi)型:第七種數(shù)據(jù)類(lèi)型。
下面我們來(lái)看一個(gè)場(chǎng)景,也許 Symbol 能派上用場(chǎng)。
一個(gè)布爾值引出的問(wèn)題
有時(shí),把一些屬于其他對(duì)象的數(shù)據(jù)暫存在另一個(gè)對(duì)象中是非常方便的。例如,假設(shè)你正在編寫(xiě)一個(gè) JS 庫(kù),使用 CSS 中的 transition 來(lái)讓一個(gè) DOM 元素在屏幕上飛奔,你已經(jīng)知道不能同時(shí)將多個(gè) transition 應(yīng)用在同一個(gè) div 上,否則將使得動(dòng)畫(huà)非常不美觀,你也確實(shí)有辦法來(lái)解決這個(gè)問(wèn)題,但是首先你需要知道該 div 是否已經(jīng)在移動(dòng)中。
怎么解決這個(gè)問(wèn)題呢?
其中一個(gè)方法是使用瀏覽器提供的 API 來(lái)探測(cè)元素是否處于動(dòng)畫(huà)狀態(tài),但殺雞焉用牛刀,在將元素設(shè)置為移動(dòng)時(shí),你的庫(kù)就知道了該元素正在移動(dòng)。
你真正需要的是一種機(jī)制來(lái)跟蹤哪些元素正在移動(dòng),你可以將正在移動(dòng)的元素保存在一個(gè)數(shù)組中,每次要為一個(gè)元素設(shè)置動(dòng)畫(huà)時(shí),首先檢查一下這個(gè)元素是否已經(jīng)在這個(gè)列表中。
啊哈,但是如果你的數(shù)組非常龐大,即便是這樣的線性搜索也會(huì)產(chǎn)生性能問(wèn)題。
那么,你真正想做的就是直接在元素上設(shè)置一個(gè)標(biāo)志:
if (element.isMoving) {
smoothAnimations(element);
}
element.isMoving = true;
if (element.isMoving) {
smoothAnimations(element);
}
element.isMoving = true;
這也有一些潛在的問(wèn)題,不得不承認(rèn)這樣一個(gè)事實(shí):還有其他代碼也可能操作該 ODM 元素。
- 在其他代碼中,你創(chuàng)建的屬性會(huì)被 for-in 或 Object.keys() 枚舉出來(lái);
- 在其他一些庫(kù)中也許已經(jīng)使用了同樣的方式(在元素上設(shè)置了相同的屬性),那么這將和你的代碼發(fā)生沖突,產(chǎn)生不可預(yù)計(jì)的結(jié)果;
- 其他一些庫(kù)可能在將來(lái)會(huì)使用同樣的方式,這也會(huì)與你的代碼發(fā)生沖突;
- 標(biāo)準(zhǔn)委員會(huì)可能會(huì)為每個(gè)元素添加一個(gè) .isMoving() 原生方法,那么你的代碼就徹底不能工作了。
當(dāng)然,對(duì)于最后三個(gè)問(wèn)題,你可以選擇一個(gè)無(wú)意義的不會(huì)有人會(huì)使用到的字符串:
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;
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;
這似乎太不靠譜了,看了讓人眼睛痛。
你還可以用加密算法來(lái)生成一個(gè)幾乎唯一的字符串:
// get 1024 Unicode characters of gibberish
var isMoving = SecureRandom.generateName();
...
if (element[isMoving]) {
smoothAnimations(element);
}
element[isMoving] = true;
// get 1024 Unicode characters of gibberish
var isMoving = SecureRandom.generateName();
...
if (element[isMoving]) {
smoothAnimations(element);
}
element[isMoving] = true;
object[name] 語(yǔ)法允許我們將任何字符串作為屬性名,代碼能正常工作,沖突幾乎是不可能了,代碼看起來(lái)也美觀多了。
但是,這回導(dǎo)致糟糕的調(diào)試體驗(yàn),每次使用 console.log() 打印出包含該屬性的元素時(shí),你回看到一個(gè)龐大的垃圾字符串,并且如果還不止一個(gè)這樣的屬性呢?每次刷新后屬性名都發(fā)生了變化,怎么樣使這些屬性看起來(lái)更加直觀呢?
為什么這么難?我們只是為了保存一個(gè)小小的標(biāo)志位。
用 Symbol 來(lái)解決問(wèn)題
Symbol 值可以由程序創(chuàng)建,并可以作為屬性名,而且不用擔(dān)心屬性名沖突。
var mySymbol = Symbol(); var mySymbol = Symbol();
調(diào)用 Symbol() 方法將創(chuàng)建一個(gè)新的 Symbol 類(lèi)型的值,并且該值不與其它任何值相等。
與數(shù)字和字符串一樣,Symbol 類(lèi)型的值也可以作為對(duì)象的屬性名,正是由于它不與任何其它值相等,對(duì)應(yīng)的屬性也不會(huì)發(fā)生沖突:
obj[mySymbol] = "ok!"; // guaranteed not to collide console.log(obj[mySymbol]); // ok! obj[mySymbol] = "ok!"; // guaranteed not to collide console.log(obj[mySymbol]); // ok!
下面是使用 Symbol 來(lái)解決上面的問(wèn)題:
// create a unique symbol
var isMoving = Symbol("isMoving");
...
if (element[isMoving]) {
smoothAnimations(element);
}
element[isMoving] = true;
// create a unique symbol
var isMoving = Symbol("isMoving");
...
if (element[isMoving]) {
smoothAnimations(element);
}
element[isMoving] = true;
上面代碼需要注意幾點(diǎn):
- 方法 Symbol("isMoving") 中的 "isMoving" 字符串被稱(chēng)為 Symbol 的描述信息,這對(duì)調(diào)試非常有幫助??梢酝ㄟ^(guò) console.log(isMoving) 打印出來(lái),或通過(guò) isMoving.toString() 將 isMoving 轉(zhuǎn)換為字符串時(shí),或在一些錯(cuò)誤信息中顯示出來(lái)。
- element[isMoving] 訪問(wèn)的是 symbol-keyed 屬性,除了屬性名是 Symbol 類(lèi)型的值之外,與其它屬性都一樣。
- 和數(shù)組一樣,symbol-keyed 屬性不能通過(guò) . 操作符來(lái)訪問(wèn),必須使用方括號(hào)的方式。
- 操作 symbol-keyed 屬性也非常方便,通過(guò)上面代碼我們已經(jīng)知道如何獲取和設(shè)置 element[isMoving] 的值,我們還可以這樣使用:if (isMoving in element) 或 delete element[isMoving]。
- 另一方面,只有在 isMoving 的作用域范圍內(nèi)才可以使用上述代碼,這可以實(shí)現(xiàn)弱封裝機(jī)制:在一個(gè)模塊內(nèi)創(chuàng)建一些 Symbol,只有在該模塊內(nèi)部的對(duì)象才能使用,而不用擔(dān)心與其它模塊的代碼發(fā)生沖突。
由于 Symbol 的設(shè)計(jì)初衷是為了避免沖突,當(dāng)遍歷 JavaScript 對(duì)象時(shí),并不會(huì)枚舉到以 Symbol 作為建的屬性,比如,for-in 循環(huán)只會(huì)遍歷到以字符串作為鍵的屬性,Object.keys(obj)和 Object.getOwnPropertyNames(obj) 也一樣,但這并不意味著 Symbol 為鍵的屬性是不可枚舉的:使用 Object.getOwnPropertySymbols(obj) 這個(gè)新方法可以枚舉出來(lái),還有 Reflect.ownKeys(obj) 這個(gè)新方法可以返回對(duì)象中所有字符串和 Symbol 鍵。(我將在后面的文章中詳細(xì)介紹 Reflect 這個(gè)新特性。)
庫(kù)和框架的設(shè)計(jì)者將會(huì)發(fā)現(xiàn)很多 Symbol 的用途,稍后我們將看到,JavaScript 語(yǔ)言本身也對(duì)其有廣泛的應(yīng)用。
Symbol 究竟是什么呢
> typeof Symbol() "symbol" > typeof Symbol() "symbol"
Symbol 是完全不一樣的東西。一旦創(chuàng)建后就不可更改,不能對(duì)它們?cè)O(shè)置屬性(如果在嚴(yán)格模式下嘗試這樣做,你將得到一個(gè) TypeError)。它們可以作為屬性名,這時(shí)它們和字符串的屬性名沒(méi)有什么區(qū)別。
另一方面,每個(gè) Symbol 都是獨(dú)一無(wú)二的,不與其它 Symbol 重復(fù)(即便是使用相同的 Symbol 描述創(chuàng)建),創(chuàng)建一個(gè) Symbol 就跟創(chuàng)建一個(gè)對(duì)象一樣方便。
ES6 中的 Symbol 與傳統(tǒng)語(yǔ)言(如 Lisp 和 Ruby)中的 Symbol 中的類(lèi)似,但并不是完全照搬到 JavaScript 中。在 Lisp 中,所有標(biāo)識(shí)符都是 Symbol;在 JavaScript 中,標(biāo)識(shí)符和大多數(shù)屬性仍然是字符串,Symbol 只是提供了一個(gè)額外的選擇。
值得注意的是:與其它類(lèi)型不同的是,Symbol 不能自動(dòng)被轉(zhuǎn)換為字符串,當(dāng)嘗試將一個(gè) Symbol 強(qiáng)制轉(zhuǎn)換為字符串時(shí),將返回一個(gè) 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
> 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
應(yīng)該避免這樣的強(qiáng)制轉(zhuǎn)換,應(yīng)該使用 String(sym) 或 sym.toString() 來(lái)轉(zhuǎn)換。
獲取 Symbol 的三種方法
- Symbol() 每次調(diào)用時(shí)都返回一個(gè)唯一的 Symbol。
- Symbol.for(string) 從 Symbol 注冊(cè)表中返回相應(yīng)的 Symbol,與上個(gè)方法不同的是,Symbol 注冊(cè)表中的 Symbol 是共享的。也就是說(shuō),如果你調(diào)用 Symbol.for("cat") 三次,都將返回相同的 Symbol。當(dāng)不同頁(yè)面或同一頁(yè)面不同模塊需要共享 Symbol 時(shí),注冊(cè)表就非常有用。
- Symbol.iterator 返回語(yǔ)言預(yù)定義的一些 Symbol,每個(gè)都有其特殊的用途。
如果你仍不確定 Symbol 是否有用,那么接下來(lái)的內(nèi)容將非常有趣,因?yàn)槲覍槟阊菔?Symbol 的實(shí)際應(yīng)用。
Symbol 在 ES6 規(guī)范中的應(yīng)用
我們已經(jīng)知道可以使用 Symbol 來(lái)避免代碼沖突。之前在介紹 iterator 時(shí),我們還解析了 for (var item of myArray) 內(nèi)部是以調(diào)用 myArray[Symbol.iterator]() 開(kāi)始的,當(dāng)時(shí)我提到這個(gè)方法可以使用 myArray.iterator() 來(lái)代替,但是使用 Symbol 的后向兼容性更好。
在 ES6 中還有一些地方使用到了 Symbol。(這些特性還沒(méi)有在 FireFox 中實(shí)現(xiàn)。)
- 使 instanceof 可擴(kuò)展。在 ES6 中,object instanceof constructor 表達(dá)式被標(biāo)準(zhǔn)化為構(gòu)造函數(shù)的一個(gè)方法:constructor[Symbol.hasInstance](object),這意味著它是可擴(kuò)展的。
- 消除新特性和舊代碼之間的沖突。
- 支持新類(lèi)型的字符串匹配。在 ES5 中,調(diào)用 str.match(myObject) 時(shí),首先會(huì)嘗試將 myObject 轉(zhuǎn)換為 RegExp 對(duì)象。在 ES6 中,首先將檢查 myObject 中是否有 myObject[Symbol.match](str) 方法,在所有正則表達(dá)式工作的地方都可以提供一個(gè)自定義的字符串解析方法。
這些用途還比較窄,但僅僅通過(guò)我文章中的代碼很難看到這些新特性產(chǎn)生的重大影響。JavaScript 的 Symbol 是 PHP 和 Python 中 __doubleUnderscores 的改進(jìn)版本,標(biāo)準(zhǔn)組織將使用它來(lái)為語(yǔ)言添加新特性,而不會(huì)對(duì)已有代碼產(chǎn)生影響。
- JavaScript原始數(shù)據(jù)類(lèi)型Symbol的用法詳解
- JS獲取對(duì)象屬性API匯總枚舉symbol
- JavaScript第七種數(shù)據(jù)類(lèi)型Symbol的用法詳解
- 詳解JavaScript的Symbol類(lèi)型、隱藏屬性、全局注冊(cè)表
- 詳解JavaScript原始數(shù)據(jù)類(lèi)型Symbol
- Javascript Symbol原理及使用方法解析
- 詳解JavaScript 為什么要有 Symbol 類(lèi)型?
- Javascript ES6中數(shù)據(jù)類(lèi)型Symbol的使用詳解
- JavaScript新引入的原始數(shù)據(jù)類(lèi)型Symbol詳解
相關(guān)文章
JavaScript那些不經(jīng)意間發(fā)生的數(shù)據(jù)類(lèi)型自動(dòng)轉(zhuǎn)換
JavaScript可以自由的進(jìn)行數(shù)據(jù)類(lèi)型轉(zhuǎn)換,但是更多的情況下,是由JavaScript自動(dòng)轉(zhuǎn)換的。本文就將為大家詳細(xì)講解那些不經(jīng)意間發(fā)生的數(shù)據(jù)類(lèi)型轉(zhuǎn)換,感興趣的同學(xué)可以了解一下2022-02-02
JavaScript高級(jí)程序設(shè)計(jì)(第3版)學(xué)習(xí)筆記6 初識(shí)js對(duì)象
砌好墻,下面出場(chǎng)的就是房子了,在ECMAScript中,對(duì)象就是我們所說(shuō)的房子,至于你所寫(xiě)的整個(gè)應(yīng)用程序,那就是一整套建筑群了2012-10-10
「中高級(jí)前端面試」JavaScript手寫(xiě)代碼無(wú)敵秘籍(推薦)
這篇文章主要介紹了JavaScript手寫(xiě)代碼,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2019-04-04
JS div勻速移動(dòng)動(dòng)畫(huà)與變速移動(dòng)動(dòng)畫(huà)代碼實(shí)例
這篇文章主要介紹了JS div勻速移動(dòng)動(dòng)畫(huà)與變速移動(dòng)動(dòng)畫(huà),文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2019-03-03
Javascript中Date類(lèi)型和Math類(lèi)型詳解
這篇文章主要給大家介紹了Javascript中Date類(lèi)型和Math類(lèi)型的一些基礎(chǔ)知識(shí),非常的實(shí)用,有需要的小伙伴可以參考下2016-02-02
Javascript的時(shí)間戳和php的時(shí)間戳轉(zhuǎn)換注意事項(xiàng)
需要注意的是js的時(shí)間戳是13位,php的時(shí)間戳是10位,轉(zhuǎn)換函數(shù)如下,感興趣的朋友可以參考下哈2013-04-04
JavaScript中g(shù)etUTCMinutes()方法的使用詳解
這篇文章主要介紹了JavaScript中g(shù)etUTCMinutes()方法的使用詳解,是JS入門(mén)學(xué)習(xí)中的基礎(chǔ)知識(shí),需要的朋友可以參考下2015-06-06
javascript窗口寬高,鼠標(biāo)位置,滾動(dòng)高度(詳細(xì)解析)
javascript窗口寬高,鼠標(biāo)位置,滾動(dòng)高度(詳細(xì)解析)。需要的朋友可以過(guò)來(lái)參考下,希望對(duì)大家有所幫助2013-11-11

