什么是 ES6 的符號(hào)?
符號(hào)不是圖標(biāo)。
他們不是你可以在你的程序中使用的小圖片。
let http://wiki.jikexueyuan.com/project/es-six-deeply/images/1.png" alt="" />= http://wiki.jikexueyuan.com/project/es-six-deeply/images/0.png" alt="" /> × http://wiki.jikexueyuan.com/project/es-six-deeply/images/2.png" alt="" />; // SyntaxError
他們不是代表別的東西的文學(xué)的工具。
他們是絕對(duì)是與鈸不一樣的東西。
http://wiki.jikexueyuan.com/project/es-six-deeply/images/bo.jpg" alt="Alt text" />
在編程中使用鈸絕對(duì)不是一個(gè)好主意。他們有奔潰的趨勢。
所以,符號(hào)到底是什么?
從 1997 年 JavaScript 第一次被標(biāo)準(zhǔn)化,已經(jīng)出現(xiàn)了六種類型。直到 ES6,在 JS 程序的每個(gè)值都落入這些類別之一。
每種類型是一組值。前五個(gè)組都是有限的。當(dāng)然,只有兩個(gè)布爾值,真與假,而且他們不會(huì)制造新的。但是有更多的數(shù)值和字符串值。該標(biāo)準(zhǔn)說,其有 18,437,736,874,454,810,627 個(gè)不同的數(shù)字(包括 NaN,則其名字為“不是一個(gè)數(shù)”的縮寫)。這與可能的字串的數(shù)量是不能相比的。我認(rèn)為字符串的數(shù)目可以是(2144,115,188,075,855,872 - 1)÷65,535...雖然我可能計(jì)算有誤。
然而,每組對(duì)象的值是開放式的。每個(gè)對(duì)象都是一個(gè)獨(dú)特的,珍貴的雪花。每一次你打開一個(gè)網(wǎng)頁,很多很多新的對(duì)象被創(chuàng)建。
ES6 符號(hào)是值,但它們不是字符串。他們不是對(duì)象。他們是新的東西:第七種類型的值。
有時(shí),在一個(gè) JavaScript 對(duì)象上藏匿一些真正屬于別人的額外的數(shù)據(jù)是非常方便的。
例如,假設(shè)你正在寫一個(gè) JS 庫,其使用 CSS 過渡,使 DOM 元素在屏幕上壓縮。你可能已經(jīng)注意到,試圖將多個(gè) CSS 轉(zhuǎn)換同時(shí)用于單個(gè) div 是不起作用的。它會(huì)導(dǎo)致難看的,不連續(xù)的“跳躍”。你認(rèn)為你能解決這個(gè)問題,但首先你需要一種方法來搞清楚一個(gè)給定的元素是否已經(jīng)移動(dòng)。
你怎么能解決這個(gè)問題?
一種方法是使用 CSS API 來詢問瀏覽器該元件是否被移動(dòng)。但是,這聽起來有點(diǎn)小題大做。你的庫應(yīng)該已經(jīng)知道元素是移動(dòng)的;那就是最開始設(shè)置其移動(dòng)的代碼!
你真正想要的是一種能跟蹤哪些元素正在移動(dòng)的方法。你可以使用一個(gè)數(shù)組紀(jì)錄所有運(yùn)動(dòng)的元素。每當(dāng)您的庫被調(diào)用,要使元素進(jìn)行移動(dòng),你可以搜索數(shù)組來看看是否該元素已經(jīng)存在。
嗯。如果數(shù)組很大的話,線性搜索將是緩慢的。
你真正想要做的只是設(shè)置元素的標(biāo)志:
if (element.isMoving) {
smoothAnimations(element);
}
element.isMoving = true;
這里也有幾個(gè)可能的問題。他們都與這樣的事實(shí)相關(guān),那就是你的代碼不是唯一一個(gè)使用 DOM 的代碼。
其他的使用 for-in 或者 Object.keys( ) 的代碼可能會(huì)在你創(chuàng)建的屬性上遇到問題。
如果一些聰明的庫作者可能已經(jīng)先想到了這個(gè)技術(shù),那么你的庫可能與現(xiàn)有的庫的互動(dòng)不是很好。
其他一些聰明的庫的作者可能在未來想到這個(gè)技術(shù),和您的庫可能與未來的庫的互動(dòng)不是很好。
當(dāng)然,你可以通過選擇一個(gè)太繁瑣或太傻了字符串來解決最后三個(gè)問題,沒有人會(huì)用下面的方式進(jìn)行命名:
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;
這看起來似乎不太值得我們眼睛的疲勞。
你可以通過使用密碼學(xué)的知識(shí)為這個(gè)屬性生成唯一的名字:
// get 1024 Unicode characters of gibberish
var isMoving = SecureRandom.generateName();
...
if (element[isMoving]) {
smoothAnimations(element);
}
element[isMoving] = true;
object[name] 語法讓你可以使用任何字符串作為一個(gè)屬性的名字。所以這將是有用的:因?yàn)榕鲎矌缀跏遣豢赡艿?,你的代碼看起來不錯(cuò)。
但是這也會(huì)導(dǎo)致很糟糕的代碼調(diào)試過程。每次你對(duì)一個(gè)有該屬性的元素進(jìn)行 console.log( ) 操作,你將會(huì)看到一個(gè)龐大的垃圾字符串。同時(shí),如果你需要不止一個(gè)像這樣的屬性呢?你怎么直接保留他們?當(dāng)你重載他們的時(shí)候,他們每次都會(huì)有不同的名字。
為什么這是如此的困難?我們只是想要一個(gè)小布爾值。
符號(hào)是值,編程者可以創(chuàng)建并像屬性關(guān)鍵字一樣使用,且不會(huì)有名字沖突的危險(xiǎn)。
var mySymbol = Symbol();
調(diào)用 Symbol( ) 可以創(chuàng)建一個(gè)新的符號(hào),且具有唯一的值。
就像一個(gè)字符串或者數(shù)值,你可以使用一個(gè)符號(hào)作為一個(gè)屬性關(guān)鍵字。因?yàn)槠洳煌谌魏巫址?,這個(gè)符號(hào)鍵屬性被用來保證不與其他的屬性相沖突。
obj[mySymbol] = "ok!"; // guaranteed not to collide
console.log(obj[mySymbol]); // ok!
這里就是你如何在上述討論的情況下使用一個(gè)符號(hào):
// create a unique symbol
var isMoving = Symbol("isMoving");
...
if (element[isMoving]) {
smoothAnimations(element);
}
element[isMoving] = true;
下述是關(guān)于這個(gè)代碼的一些筆記。
在符號(hào)( "isMoving")中的字符串 "isMoving"被稱為一個(gè)描述。它有助于代碼調(diào)試。當(dāng)你給 console.log( )寫一個(gè)符號(hào)的時(shí)候,當(dāng)你通過使用 .toString( )將其轉(zhuǎn)化為一個(gè)字符串的時(shí)候,亦或者可能在錯(cuò)誤信息中你可以感覺到它的好處。就是這樣的。
element[isMoving] 被稱為一個(gè)符號(hào)鍵屬性。它只是一種名字是一個(gè)符號(hào)的屬性,除此之外,它是在各方面都是一個(gè)正常屬性。
如數(shù)組元素相似,符號(hào)鍵屬性不能像在 obj.name 中用點(diǎn)語法訪問。他們必須使用方括號(hào)進(jìn)行訪問。
如果你已經(jīng)拿到了符號(hào),訪問符號(hào)鍵屬性是微不足道的。上面的例子就是說明如何獲取和設(shè)置 element[isMoving],而且我們還可以詢問是否為 (isMoving in element),或者如果我們需要,我們甚至可以使用 delete element[isMoving]。
由于符號(hào)關(guān)鍵字被設(shè)計(jì)為避免沖突的,JavaScript 最常見的對(duì)象檢測功能簡單地忽視了符號(hào)鍵。例如,一個(gè) for-in 循環(huán),其僅在對(duì)象的字符串關(guān)鍵字上循環(huán)。符號(hào)關(guān)鍵字是被跳過的。 Object.keys(obj) 和 Object.getOwnPropertyNames(obj) 也是這樣做的。但符號(hào)不完全是私有的:它可以使用新的 API Object.getOwnPropertySymbols(obj) 來列出一個(gè)對(duì)象的符號(hào)關(guān)鍵字。另一個(gè)新的 API,Reflect.ownKeys(obj),返回字符串和符號(hào)關(guān)鍵字。(我們將在后續(xù)的博客中繼續(xù)討論 Reflect API。)
庫和框架很可能會(huì)發(fā)現(xiàn)符號(hào)的很多用途,如我們以后會(huì)看到的一樣,語言本身使用他們是為了更加廣泛的用途。
> typeof Symbol()
"symbol"
符號(hào)不像任何其他東西。
他們一旦創(chuàng)建就不可改變。你不能對(duì)它們進(jìn)行屬性設(shè)置(如果你在嚴(yán)格模式下嘗試,你會(huì)得到一個(gè)類型錯(cuò)誤)。它們可以是屬性的名稱。這些都是與字符串類似的性質(zhì)。
在另一方面,每個(gè)符號(hào)都是獨(dú)一無二的,與所有其他的符號(hào)都不相同(即使其他人有相同的描述)。同時(shí),你可以很輕松地創(chuàng)建新的符號(hào)。這些是與對(duì)象相類似的性質(zhì)。
ES6 符號(hào)類似于相對(duì)傳統(tǒng)的語言中的 Lisp 和 Ruby,但其又沒有非常緊密的集成于語言中。在 Lisp 中,所有標(biāo)識(shí)符都是符號(hào)。在 JS 中,標(biāo)識(shí)符和大部分屬性關(guān)鍵字仍然被認(rèn)為是字符串。符號(hào)只是一種額外的選擇。
關(guān)于符號(hào)做個(gè)快速的警告:與幾乎所有其他語言都不同的是,它們不能自動(dòng)轉(zhuǎn)換為字符串。試圖用講符號(hào)和字符串串聯(lián)起來將導(dǎo)致一個(gè)類型錯(cuò)誤。
> 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
你可以通過明確的將一個(gè)符號(hào)轉(zhuǎn)換為一個(gè)字符串來避免發(fā)生類型錯(cuò)誤。方法就是 String(sym) 或者 sym.toString( )。
獲取一個(gè)符號(hào)有三種方式。
調(diào)用 Symbol( )。正如我們已經(jīng)討論過的,每次被調(diào)用后,其都會(huì)返回一個(gè)新的獨(dú)一無二的符號(hào)。
調(diào)用 Symbol.for(string)。訪問一組已經(jīng)存在的字符串被稱為符號(hào)注冊。不同于由 Symbol( ) 定義的獨(dú)特的符號(hào),在符號(hào)注冊表中的符號(hào)是共享的。如果你調(diào)用Symbol.for("cat") 三十多次,其每次都將返回相同的符號(hào)。當(dāng)多個(gè)網(wǎng)頁,或同一網(wǎng)頁內(nèi)多個(gè)模塊需要共享一個(gè)符號(hào)時(shí),注冊表是有用的。
如果你仍然不確定符號(hào)是否都是都如此有效,這里最后的部分將是有趣的,因?yàn)樗麄儗?huì)展示符號(hào)是如何在實(shí)際中被證明是有效的。
我們已經(jīng)看到,ES6 使用符號(hào)作為一種避免與現(xiàn)有代碼的沖突的方法。幾個(gè)星期前,在講迭代器的博客中,我們看到,循環(huán) for (var item of myArray) 通過調(diào)用 myArray[Symbol.iterator]( ) 方法開始。我提到的這個(gè)方法已經(jīng)被稱為 myArray.iterator( ),但符號(hào)能更好得向后兼容。
現(xiàn)在我們知道了符號(hào)是怎么一回事了,因此很容易理解為什么這樣做且這樣做意味著什么。
這里還有幾個(gè)地方 ES6 使用了眾所周知的符號(hào)。(這些功能都還沒有在火狐瀏覽器中實(shí)現(xiàn)。)
使得 instanceof 可擴(kuò)展。在 ES6 中,語句 object instanceof constructor 被具體描述為構(gòu)造的一個(gè)方法:constructor[Symbol.hasInstance](object)。這就意味著這是可擴(kuò)展的。
消除了新功能和舊代碼之間的沖突。這真的很復(fù)雜,但是我們發(fā)現(xiàn) ES6 的數(shù)組方法只是通過其存在性就使現(xiàn)有的網(wǎng)站奔潰了。其它的 Web 標(biāo)準(zhǔn)有類似的問題:在瀏覽器里簡單地增加新的方法也會(huì)使現(xiàn)有的網(wǎng)站崩潰。然而,奔潰主要是由于一種叫做動(dòng)態(tài)作用域的東西,所以 ES6 引入了一個(gè)特殊的符號(hào),Symbol.unscopables,即 Web 標(biāo)準(zhǔn)可以用這個(gè)符號(hào)防止某些方法被卷入到動(dòng)態(tài)作用域中。
上述的任意一個(gè)的用途都比較窄。很難從這些功能自身來看其對(duì)我們的代碼產(chǎn)生的重大影響。從長遠(yuǎn)來看更有趣。著名的符號(hào)是 JavaScript 對(duì) PHP 和 Python 的 __doubleUnderscores 的改進(jìn)版本。該標(biāo)準(zhǔn)將在未來,在對(duì)你現(xiàn)有的代碼不產(chǎn)生風(fēng)險(xiǎn)的前提下,使用他們在語言中添加新的掛鉤,
符號(hào)已經(jīng)在火狐瀏覽器第 36 版本和谷歌瀏覽器第 38 版本中實(shí)現(xiàn)了。我自己也在火狐瀏覽器上進(jìn)行了實(shí)現(xiàn),所以如果你的符號(hào)曾經(jīng)表現(xiàn)得像鈸,你就會(huì)知道應(yīng)該向誰傾訴。
為了支持那些對(duì) ES6 符號(hào)還沒有原生支持的瀏覽器,你可以使用一個(gè) polyfill,如 core.js。由于符號(hào)與以前語言中的東西是完全不像的,polyfill 也不是完美的。請(qǐng)閱讀注意事項(xiàng)。
下章我們將討論一些 JavaScript 期待已久且終于在 ES6 中到來的功能,并吐糟一下它不好的地方。我們將開始兩個(gè)可以追溯到編程最初時(shí)光的功能。我們將繼續(xù)討論兩個(gè)非常相似的但由 ephemerons 搭載的功能。