在线观看不卡亚洲电影_亚洲妓女99综合网_91青青青亚洲娱乐在线观看_日韩无码高清综合久久

鍍金池/ 教程/ HTML/ 容器
生成器
箭頭函數(shù)
繼續(xù)迭代器
未來(lái)前景
簡(jiǎn)介
非結(jié)構(gòu)化賦值
迭代器和 for-of 循環(huán)
代理
符號(hào)
模版字符串
let 和 const
容器
模塊

容器

關(guān)于 ES6 的規(guī)范,官方命名為 ECMA-262 第六版本。采用的 ECMAScript 2015 語(yǔ)言規(guī)范,清除了最后的障礙并得到了 Ecma 組織的認(rèn)可。祝賀 TC39 和每個(gè)為 ES6 規(guī)范做出貢獻(xiàn)的人。這本書(shū)就講解了 ES6 !

更好的消息:下一個(gè)版本更新將不會(huì)再是六年之后。標(biāo)準(zhǔn)協(xié)會(huì)計(jì)劃在十二個(gè)月內(nèi)產(chǎn)生一個(gè)新版本。關(guān)于第七版本已經(jīng)在開(kāi)發(fā)中。

現(xiàn)在是時(shí)候來(lái)慶祝關(guān)于 JS 的最新版本,并且我仍然認(rèn)為 JS 的未來(lái)還有很大提升。

艱難地共同進(jìn)化

JS 與其它編程語(yǔ)言不太一樣,有時(shí)候它能以一種意想不到的方式在影響其他語(yǔ)言的發(fā)展。

ES6 模塊就是一個(gè)很好的例子。其他語(yǔ)言也有模塊系統(tǒng)。Racket 語(yǔ)言,Python 語(yǔ)言都有。當(dāng)標(biāo)準(zhǔn)協(xié)會(huì)決定加入模塊到 ES6 當(dāng)中,為什么不直接復(fù)制已經(jīng)存在的模塊系統(tǒng)呢?

JS 是不一樣的,因?yàn)樗侵苯釉?web 瀏覽器上運(yùn)行。I/O 操作將耗費(fèi)很長(zhǎng)的時(shí)間。這樣, JS 需要一個(gè)提供異步支持的模塊系統(tǒng)。它不能忍受在多個(gè)目錄下搜索模塊。顯然,復(fù)制已有的系統(tǒng)是不可取的。這樣 ES6 模塊系統(tǒng)需要一些新的東西。

關(guān)于如何影響最終的設(shè)計(jì)是一個(gè)有趣的故事。但是這里我們不討論。

這一部分關(guān)于 ES6 標(biāo)準(zhǔn)被稱為 “鍵值的容器” :Set,Map,WeakSet,和 WeakMap。在大多數(shù)方面,這些 “鍵值容器” 的特征類似其他語(yǔ)言的哈希表。但是標(biāo)準(zhǔn)協(xié)做了一些有趣的事來(lái)權(quán)衡 “鍵值容器” 和哈希表,因?yàn)?JS 是與眾不同的。

為什么會(huì)有容器?

任何一個(gè)對(duì) JS 熟悉的人都知道 JS 中已經(jīng)存在和哈希表類似的結(jié)構(gòu): objects 。

一個(gè)純對(duì)象畢竟只是一個(gè)開(kāi)放式的鍵-值對(duì)的集合。你可以獲得,設(shè)置,和刪除屬性,或者迭代——所有哈希表可以做的操作。因此,為什么需要添加一個(gè)新的特征呢?

許多程序用純對(duì)象來(lái)存儲(chǔ)鍵-值對(duì),并且在程序運(yùn)行很好的情況下,沒(méi)有特殊原因的選擇 Map 或者 Set 。這里仍然存在一些關(guān)于使用對(duì)象(objects )所存在的眾所周知的問(wèn)題:

  • Objects 被用作查找表的時(shí)候,沒(méi)辦法在沒(méi)有沖突的情況下同時(shí)擁有方法。
  • 這樣程序要么用 Object.create(null) (而不是一個(gè)純對(duì)象 { })或者需要努力避免將內(nèi)置函數(shù) (像 Object.prototype.toString)誤以當(dāng)作數(shù)據(jù)。
  • 鍵通常是字符串類型(或者,在 ES6 中,符號(hào)類型)。對(duì)象不能被作為鍵。
  • 沒(méi)有有效的方法來(lái)獲得對(duì)象的屬性的個(gè)數(shù)。

ES6 增加了新的考慮:純對(duì)象是不可迭代的,因此它們將無(wú)法用 for-of 循環(huán)或者 ... 等操作符。

大量的程序中都認(rèn)為純對(duì)象是一個(gè)不錯(cuò)的選擇。 Map 和 Set 作為其他情況下的選擇。

因?yàn)樗鼈儽挥脕?lái)避免用戶數(shù)據(jù)和內(nèi)置方法之間的沖突,在 ES6 容器中不用公開(kāi)它們的數(shù)據(jù)屬性。這就意味著像 obj.key 或者 obj[key] 將不能被用來(lái)訪問(wèn)哈希表數(shù)據(jù)。你就只有寫(xiě) map.get(key) 了。通常,哈希表實(shí)體不像屬性一樣,不能通過(guò)原型鏈被繼承。

不像純對(duì)象一樣, Map 和 Set 提供了方法,很多方法都能在標(biāo)準(zhǔn)類或之類當(dāng)中被添加,而且沒(méi)有沖突。

Set

一個(gè) Set 是一組值的集合。該集合是可變的,所以你可以通過(guò)編程來(lái)增加或者刪除值。到目前為止, Set 就像是一個(gè)數(shù)組。盡管 Set 和數(shù)組如此的相似,但是他們兩者之間同樣存在許多差異。

首先,不像數(shù)組,一個(gè) Set不允許有重復(fù)的值。如果你試圖增加一個(gè) Set 中已經(jīng)存在的值,那么你等于什么都沒(méi)有做。

> var desserts = new Set("1","2","3","4");
> desserts.size
    4
> desserts.add("1");
    Set [ "1", "2", "3", "4" ]
> desserts.size
    4

這個(gè)例子用到了字符, Set 能夠包含 JS 提供的任意類型值。 當(dāng)只用字符串時(shí),當(dāng)你添加已經(jīng)在 Set 中存在的字符串時(shí),對(duì)于 Set 來(lái)說(shuō)沒(méi)有任何效果。

其次, 一個(gè) Set 會(huì)有效的組織它的數(shù)據(jù)以便可以高效的進(jìn)行操作:

> // Check whether "zythum" is a word.
> arrayOfWords.indexOf("zythum") !== -1  // slow
    true
> setOfWords.has("zythum")               // fast
    true

你不能根據(jù)索引獲得 Set 中的值:

> arrayOfWords[15000]
    "anapanapa"
> setOfWords[15000]   // sets don't support indexing
    undefined    

下面給出關(guān)于 Set 的所有操作:

  • new Set 創(chuàng)建一個(gè)新的空 Set。
  • new Set(iterable) 創(chuàng)建一個(gè)新的 Set 并用任意的迭代值來(lái)填充。
  • set.size 獲得 Set 的長(zhǎng)度。
  • set.has(value) 當(dāng) Set 中有這個(gè)值返回 true 。
  • set.add(value) 添加一個(gè)值到 Set 中。如果 Set 中已經(jīng)存在這個(gè)值了,那么什么也不會(huì)發(fā) 生。
  • set.delete(value) 刪除 Set 中的一個(gè)值。如果 Set 中不存在這個(gè)值,同樣什么也不會(huì)發(fā)生。 add( ) 和.delete( ) 都會(huì)返回 Set 本身,所以你必須將這些操作串起來(lái)。
  • set[Symbol.iterator]( ) 返回一個(gè) Set 上的迭代器。你通常不會(huì)直接調(diào)用,但是這個(gè)方法會(huì)使得 Set 可迭代。這就意味著你可以直接這么寫(xiě) for (v of set) {...} 。
  • set.forEach(f) 這個(gè)代碼太簡(jiǎn)單了,你可以看看下面的例子:
   for (let value of set)
       f(value, value, set);

    這個(gè)方法和數(shù)組的  .forEach( )  類似。
  • set.clear( ) 去掉 Set 中的所有值。
  • set.keys( ),set.values( ),和 set.entries( ) 返回各種迭代器。這是為了提供和 Map 類型的兼容性。關(guān)于 Map 類型將會(huì)在后續(xù)進(jìn)行講解。

在所有的這些特性中,new Set(iterable) 構(gòu)造器就像一個(gè)發(fā)電室一樣,因?yàn)樗谡麄€(gè)數(shù)據(jù)結(jié)構(gòu)上操作。你可以用它將一個(gè)數(shù)組轉(zhuǎn)換為一個(gè) Set, 用一行代碼就可以刪除重復(fù)的值?;蛘撸梢允占a(chǎn)生的值來(lái)構(gòu)造 Set 。這個(gè)構(gòu)造器也復(fù)制了現(xiàn)有的 Set 。

我上周抱怨 ES6 的新的容器?,F(xiàn)在就開(kāi)始了。盡管 Set 很便利,但是有些漏掉的方法能使未來(lái)的標(biāo)準(zhǔn)更好:

  • 功能助手像 .map( ), .filter( ),.some( ), 和 .every( ) 。
  • 不可變 set1.union(set2) 和 set1.intersection(set2) 。
  • 可以操作很多值的方法:set.addAll(iterable),set.removeAll(iterable),和 set.hasAll(iterable) 。

這里有個(gè)好消息是上面所有的方法都已經(jīng)在 ES6 中實(shí)現(xiàn)了。

Map

一個(gè) Map 是含有鍵-值對(duì)的集合。下面給出 Map 能干什么:

  • new Map 返回一個(gè)新的空 Map 。
  • new Map(pairs) 創(chuàng)建一個(gè)新的 Map , 并且從已有的 [key, value] 鍵值對(duì)來(lái)填充該 Map。鍵值對(duì)可以來(lái)自已經(jīng)存在的 Map 對(duì)象,也可以是一個(gè)含有兩個(gè)元素的數(shù)組,還可以是一個(gè)產(chǎn)生兩個(gè)元素的數(shù)組生成器,等等。
  • map.size 獲得 Map 中實(shí)體的數(shù)量。也指的是 Map 的長(zhǎng)度。
  • map.has(key) 獲得 Map 中鍵為 key 對(duì)應(yīng)的那個(gè)值。如果沒(méi)有存在這個(gè) key 鍵,得到一個(gè)未定義符。
  • map.set(key, value) 添加一個(gè)鍵值對(duì)到 Map 中,如果該 Map 已經(jīng)存在同樣的鍵,那么就把該鍵對(duì)應(yīng)的值更新為現(xiàn)在的值(就像 obj[key] = value)。
  • map.delete(key) 刪除 Map 中 key 鍵和對(duì)應(yīng)的值( 就像 delete obj[key] )。
  • map.clear( ) 刪除 Map 中的所有鍵值對(duì)。
  • map[Symbol.iterator]( ) 返回 Map 鍵 值對(duì)的一個(gè)迭代器。該迭代器像是一個(gè)數(shù)組一樣表示每個(gè)條目 [key, value] 。
  • map.forEach(f) 以下面的方式工作:
for (let [key, value] of map)
  f(value, key, map);

臨時(shí)的參數(shù)順序可以類比于 Array.prototype.forEach( ) 方法。

  • map.keys( ) 返回 Map 中所有鍵的迭代器。
  • map.values( ) 返回 Map 中所有值的迭代器。
  • map.entries( ) 返回 Map 中所有條目的迭代器,就像之前介紹的 map[Symbol.iterator]( ) 方法。事實(shí)上,就是 map[Symbol.iterator]( ) 方法的另外一個(gè)名字。

這里我們又抱怨什么呢?這里有些我認(rèn)為有用的特性,但是在 ES6 中沒(méi)有提到:

  • 一個(gè)設(shè)置默認(rèn)值的工具,就像 Python 中的 collections.defaultdict 。
  • 一個(gè)輔助函數(shù),Map.fromObject(obj) 使得用對(duì)象語(yǔ)法來(lái)構(gòu)建 Map 更簡(jiǎn)單。

這些特性都很容易添加。

OK。記得這一章節(jié)的時(shí)候我們就就埋下一個(gè)疑問(wèn),在瀏覽器中運(yùn)行這種獨(dú)特的形式到底是如何影響 JS 語(yǔ)言特性的設(shè)計(jì)呢?這里,我們將來(lái)揭開(kāi)謎底。我準(zhǔn)備了三個(gè)例子。這里先看前面兩個(gè)。

JS 是與眾不同的,第一部分:沒(méi)有哈希代碼的哈希表?

我可以告訴你,ES6 中有個(gè)有用的特性,那就是容器類不支持所有的類型。

假設(shè)我們有一個(gè)關(guān)于 URL 對(duì)象的 Set。

var urls = new Set;
urls.add(new URL(location.href));  // two URL objects.
urls.add(new URL(location.href));  // are they the same?
alert(urls.size);  // 2

上面的兩個(gè) URL 對(duì)象都應(yīng)該被視為等價(jià)的。它們有著同樣的域。但是在 JavaScript 中,這兩個(gè)對(duì)象是有區(qū)別的,并且沒(méi)有任何方式可以重載語(yǔ)言概念上的相等。所以上面的例子中最后輸出的 alert(urls.size) 是 2。

其他的語(yǔ)言支持這種語(yǔ)言概念上的相等性。 Java ,Python,和 Ruby,每個(gè)類都能支持重載相等。在許多方案的實(shí)現(xiàn)中,哈希表可以使用不同的相等關(guān)系來(lái)創(chuàng)建。 C++ 支持兩者。

然而,上面的所有機(jī)制都需要用戶實(shí)現(xiàn)傳統(tǒng)的哈希函數(shù)和所有顯示系統(tǒng)的默認(rèn)哈希函數(shù)。標(biāo)準(zhǔn)協(xié)會(huì)選擇不公開(kāi) JS 的哈希代碼——至少,不是現(xiàn)在——由于關(guān)于互用和安全的開(kāi)放性問(wèn)題,暫時(shí)沒(méi)有公開(kāi) JS 的哈希代碼。

JS 是與眾不同的,第二部分:驚喜!可預(yù)見(jiàn)性!

你可能認(rèn)為來(lái)自計(jì)算機(jī)的確定性行為很難讓人感到驚奇。但是當(dāng)我告訴開(kāi)發(fā)者后,Map 和 Set 迭代訪問(wèn)條目的順序是你們插入到容器的順序,他們通常會(huì)感到驚奇。訪問(wèn)容器的順序是確定性的。

我們習(xí)慣于認(rèn)為哈希表是任意的。我們已經(jīng)習(xí)慣接受這樣的認(rèn)識(shí)。但是避免這種任意性是有原因的。正如我在2012年所寫(xiě)的一樣:

  • 已經(jīng)有證據(jù)表明程序員當(dāng)發(fā)現(xiàn)任意迭代順序時(shí)是驚奇的或者最初是迷惑的。
  • 屬性枚舉在 ECMAScript 中未指明,為了 Web 的兼容性,大量的實(shí)現(xiàn)都強(qiáng)制覆蓋插入順序。這樣,如果 TC39 沒(méi)有明確一個(gè)確定性的迭代順序, “ web 將會(huì)為我們指定一個(gè)順序”。
  • 哈希表的迭代順序能夠公開(kāi)一些對(duì)象的哈希代碼。這將會(huì)要求哈希函數(shù)的實(shí)現(xiàn)者在實(shí)現(xiàn)哈希函數(shù)的時(shí)候考慮到完全性的因素。舉個(gè)例子,一個(gè)對(duì)象的地址不能從公開(kāi)的哈希代碼中恢復(fù)。(把對(duì)象地址展示給 ECMAScript 代碼,如果不是自身所用,將會(huì)是 Web 上的一個(gè)糟糕的安全漏洞。)

這些都在 2012 年 2 月被討論到,我贊成任意迭代順序。然后我用實(shí)驗(yàn)來(lái)展示了保持插入順序?qū)?huì)使得哈希表操作很慢。我寫(xiě)了少量的 C++ 微基準(zhǔn)。結(jié)果令我很吃驚。

這樣,我們最終在 JS 中跟蹤哈希表的插入順序。

強(qiáng)烈建議使用弱容器

上周,我們討論一個(gè)涉及到 JS 動(dòng)畫(huà)庫(kù)的例子。我們?cè)噲D在每個(gè) DOM 對(duì)象中存儲(chǔ)一個(gè)布爾類型的 flag ,就像下面這樣:

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

不幸的是,在一個(gè) DOM 對(duì)象上設(shè)置屬性是一個(gè)糟糕的想法,原稿中有討論原因。

原稿展示了如何用符號(hào)( symbols )來(lái)解決這個(gè)問(wèn)題。但是我們不能用 Set 來(lái)解決么?可以像下面這樣:

if (movingSet.has(element)) {
  smoothAnimations(element);
}
movingSet.add(element);

這樣做的方法有個(gè)缺點(diǎn):Map 和 Set 對(duì)象會(huì)對(duì)每個(gè)鍵和值保持一個(gè)強(qiáng)引用。這就意味著如果一個(gè) DOM 元素從文檔中除去,垃圾收集器不能恢復(fù)內(nèi)存直到從 movingSet 中除去。

庫(kù)函數(shù)雖然取得了成功,但強(qiáng)加給用戶使用后要清除的操作。所以這樣如果用戶忘了清除,容易造成內(nèi)存溢出。

ES6 對(duì)此提供了一個(gè)令人驚訝的修復(fù)。把 movingSet 變成一個(gè) WeakSet ,而不是一個(gè) Set。內(nèi)存溢出就會(huì)解決!

這意味著可以用弱容器或者是符號(hào)來(lái)解決這一問(wèn)題。那么哪一個(gè)更好呢?關(guān)于討論哪一種方法更好,需要很長(zhǎng)的篇幅。如果你在 web 頁(yè)面都只用一種符號(hào)完成所有操作,那么這樣做也可行。如果你需要用多個(gè)符號(hào)來(lái)完成,那么這樣做是有危險(xiǎn)的。你最好考慮用弱容器來(lái)完成。

WeakMap 和 WeakSet

WeakMap 和 WeakSet 在行為上有點(diǎn)像 Map 和 Set , 但是有些限制:

  • WeakSet 只支持 new,.has( ),.add( ),和 .delete( ) 操作。
  • WeakSet 中的值和 WeakMap 中的鍵一定要是對(duì)象。

注意到弱容器是不能迭代的。

這些精心設(shè)計(jì)的限制使得垃圾收集器能收集到死亡的對(duì)象。效果類似于你獲得弱引用或者一個(gè)弱鍵字典,但是 ES6 的弱容器使得內(nèi)存管理可以在不揭示 GC 情況下完成,這是大有益處的。

JS 是與眾不同的,第三部分:隱藏 GC 不確定性

簡(jiǎn)而言之, 一個(gè) WeakSet 對(duì)于它所包含的對(duì)象沒(méi)有一個(gè)強(qiáng)引用。當(dāng)你的 WeakSet 收集到一個(gè)對(duì)象的時(shí)候,該對(duì)象很容易被 WeakSet 刪除。WeakMap 是同樣的道理。它也不需要維護(hù)一個(gè)鍵的強(qiáng)引用。如果一個(gè)鍵時(shí)可用的,引用就是可用的。

為什么要接受這樣的限制呢?為什么要在 JS 中增加弱引用呢?

標(biāo)準(zhǔn)協(xié)會(huì)很不情愿揭示腳本的不確定性。性能不好的瀏覽器是 Web 發(fā)展的障礙。弱引用揭示了潛在垃圾收集器的實(shí)現(xiàn)細(xì)節(jié)——平臺(tái)特有的任意行為。當(dāng)然,應(yīng)用不依賴于平臺(tái)的實(shí)現(xiàn)細(xì)節(jié),但是弱引用使得我們很難知道我們對(duì)于瀏覽器有多依賴。這里很難解釋清楚。

與之相反的是, ES6 的弱容器有一些限制的特征集,但是這些特征固定的。事實(shí)上一個(gè)鍵或者一個(gè)值被收集是很難被觀察到的,因此這些應(yīng)用不能依賴于瀏覽器。

這是一個(gè)基于瀏覽器考慮的例子,從而引出了一個(gè)令人驚訝的設(shè)計(jì)決策,使 JS 成為更好的語(yǔ)言。

我們什么時(shí)候能夠在代碼中用到容器?

四種容器類目前已在 Firefox ,Chrome, Microsoft Edge 和 Safari 中實(shí)現(xiàn)。為了支持老的瀏覽器,需要用 es6-collections 工具來(lái)輔助。

在 Firefox 中, WeakMap 最早由其首席技術(shù)官 Andreas Gal, 實(shí)現(xiàn)。 Tom Schuster 實(shí)現(xiàn)了 WeakSet 。我實(shí)現(xiàn)了 Map 和 Set 。感謝 Tooru Fujisawa 提供的補(bǔ)丁。

上一篇:符號(hào)下一篇:let 和 const