<ruby id="bdb3f"></ruby>

    <p id="bdb3f"><cite id="bdb3f"></cite></p>

      <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
        <p id="bdb3f"><cite id="bdb3f"></cite></p>

          <pre id="bdb3f"></pre>
          <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

          <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
          <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

          <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                <ruby id="bdb3f"></ruby>

                ??碼云GVP開源項目 12k star Uniapp+ElementUI 功能強大 支持多語言、二開方便! 廣告
                > 原文出處:http://www.infoq.com/cn/articles/es6-in-depth-collections 前段時間,官方名為“ECMA-262,第六版,ECMAScript 2015語言規范”的ES6規范終于結束了最后的征途,正式被認可為新的ECMA標準。讓我們祝賀TC39等所有作出貢獻人們,ES6終于定稿了! 更好的消息是,下次更新不需要再等六年了。委員會現在努力要求,大約每12個月完成一個新的版本。[第七版提議](https://github.com/tc39/ecma262)已經開始。 現在是時候慶祝慶祝了,讓我們來討論一些很久以來我一直希望在JS里看到的東西——當然,它們以后仍然有改進的余地。 [TOC] ## 共同發展中的難題 JS和其它編程語言有些特殊的差別,有時,它們會以令人驚奇的方式影響到這門語言的發展。 ES6模塊就是個很好的例子。其它語言的模塊化系統中,Racket做得特別棒,Python也很好。那么,當標準委員會決定在ES6中增加模塊時,為什么他們不直接仿照一套已經存在的系統呢? 因為JS是不同的,因為它要在瀏覽器里運行。讀取和寫入都可能花費較長時間,所以,JS需要一套支持異步加載代碼的模塊化系統,同時,也不能允許在文件夾中挨個搜索,照搬已有的系統并不能解決問題。ES6的模塊化系統需要一些新技術。 討論這些問題對最終設計的影響,會是個有趣的故事,不過我們今天要討論的并不是模塊。 這篇文章是關于ES6標準中所謂“鍵值集合”的:`Set`,`Map`,`WeakSet`和`WeakMap`。它們在大多數方面和其它語言中的[哈希表](https://en.wikipedia.org/wiki/Hash_table)一樣,不過,正因為JS是不同的,標準委員會在其中做了些有趣的權衡與調整。 ## 為什么要集合? 熟悉JS一定會知道,我們已經有了一種類似哈希表的東西:對象(`Object`)。 一個普通的對象畢竟就只是一個開放的鍵值對集合。你可以進行獲取、設置、刪除、遍歷——任何一個哈希表支持的操作。所以我們到底為什么要增加新的特性? 好吧,大多數程序簡單地用對象來存儲鍵值對就夠了,對它們而言,沒什么必要換用`Map`或`Set`。但是,直接這樣使用對象有一些廣為人知的問題: * 作為查詢表使用的對象,不能既支持方法又保證避免沖突。 * 因而,要么得用`Object.create(null)`而非直接寫`{}`,要么得小心地避免把`Object.prototype.toString`之類的內置方法名作為鍵名來存儲數據。 * 對象的鍵名總是字符串(當然,ES6 中也可以是`Symbol`)而不能是另一個對象。 * 沒有有效的獲知屬性個數的方法。 ES6中又出現了新問題:純粹的對象不[可遍歷](http://www.infoq.com/cn/articles/es6-in-depth-iterators-and-the-for-of-loop),也就是,它們不能配合`for-of`循環或`...`操作符等語法。 嗯,確實很多程序里這些問題都不重要,直接用純對象仍然是正確的選擇。`Map`和`Set`是為其它場合準備的。 這些ES6中的集合本來就是為避免用戶數據與內置方法沖突而設計的,所以它們**不會**把數據作為屬性暴露出來。也就是說,`obj.key`或`obj[key]`不能再用來訪問數據了,取而代之的是`map.get(key)`。同時,不像屬性,哈希表的鍵值不能通過原型鏈來繼承了。 好消息是,不像純粹的`Object`,`Map`和`Set`有自己的方法了,并且,更多標準或自定義的方法可以無需擔心沖突地加入。 ## Set 一個`Set`是一群值的集合。它是可變的,能夠增刪元素。現在,還沒說到它和數組的區別,不過它們的區別就和相似點一樣多。 首先,和數組不同,一個`Set`不會包含相同元素。試圖再次加入一個已有元素不會產生任何效果。 ![](https://box.kancloud.cn/2015-09-20_55feb3aeeb46c.png) 這個例子里元素都是字符串,不過`Set`是可以包含JS中任何類型的值的。同樣,重復加入已有元素不會產生效果。 其次,`Set`的數據存儲結構專門為一種操作作了速度優化:包含性檢測。 ~~~ > // 檢查"zythum"是不是一個單詞 > arrayOfWords.indexOf("zythum") !== -1 // 慢 true > setOfWords.has("zythum") // 快 true ~~~ `Set`不能提供的則是索引。 ~~~ > arrayOfWords[15000] "anapanapa" > setOfWords[15000] // Set不支持索引 undefined ~~~ 以下是`Set`支持的所有操作: * `new Set`:創建一個新的、空的`Set`。 * `new Set(iterable)`:從任何[可遍歷數據](https://hacks.mozilla.org/2015/04/es6-in-depth-iterators-and-the-for-of-loop/)中提取元素,構造出一個新的集合。 * `set.size`:獲取集合的大小,即其中元素的個數。 * `set.has(value)`:判定集合中是否含有指定元素,返回一個布爾值。 * `set.add(value)`:添加元素。如果與已有重復,則不產生效果。 * `set.delete(value)`:刪除元素。如果并不存在,則不產生效果。`.add()`和`.delete()`都會返回集合自身,所以我們可以用鏈式語法。 * `set[Symbol.iterator]()`:返回一個新的遍歷整個集合的迭代器。一般這個方法不會被直接調用,因為實際上就是它使集合能夠被遍歷,也就是說,我們可以直接寫`for (v of set) {...}`等等。 * `set.forEach(f)`:直接用代碼來解釋好了,它就像是`for (let value of set) { f(value, value, set); }`的簡寫,類似于數組的`.forEach()`方法。 * `set.clear()`:清空集合。 * `set.keys()`、`set.values()`和`set.entries()`返回各種迭代器,它們是為了兼容`Map`而提供的,所以我們待會兒再來看。 在這些特性中,負責構造集合的`new Set(iterable)`是唯一一個在整個數據結構層面上操作的。你可以用它把數組轉化為集合,在一行代碼內去重;也可以傳遞一個生成器,函數會逐個遍歷它,并把生成的值收錄為一個集合;也可以用來復制一個已有的集合。 上周我答應過要給ES6中的新集合們挑挑刺,就從這里開始吧。盡管`Set`已經很不錯了,還是有些被遺漏的方法,說不定補充到將來某個標準里會挺不錯: * 目前數組已經有的一些輔助函數,比如`.map()`、`.filter()`、`.some()`和`.every()`。 * 不改變原值的交并操作,比如`set1.union(set2)`和`set1.intersection(set2)`。 * 批量操作,如`set.addAll(iterable)`、`set.removeAll(iterable)`和`set.hasAll(iterable)`。 好消息是,這些都可以用ES6已經提供了的方法來實現。 ## Map 一個`Map`對象由若干鍵值對組成,支持: * `new Map`:返回一個新的、空的`Map`。 * `new Map(pairs)`:根據所含元素形如`[key, value]`的數組`pairs`來創建一個新的`Map`。這里提供的`pairs`可以是一個已有的`Map`?對象,可以是一個由二元數組組成的數組,也可以是逐個生成二元數組的一個生成器,等等。 * `map.size`:返回`Map`中項目的個數。 * `map.has(key)`:測試一個鍵名是否存在,類似`key in obj`。 * `map.get(key)`:返回一個鍵名對應的值,若鍵名不存在則返回`undefined`,類似`obj[key]`。 * `map.set(key, value)`:添加一對新的鍵值對,如果鍵名已存在就覆蓋。 * `map.delete(key)`:按鍵名刪除一項,類似`delete obj[key]`。 * `map.clear()`:清空`Map`。 * `map[Symbol.iterator]()`:返回遍歷所有項的迭代器,每項用一個鍵和值組成的二元數組表示。 * `map.forEach(f)`?類似`for (let [key, value] of map) { f(value, key, map); }`。這里詭異的參數順序,和`Set`中一樣,是對應著`Array.prototype.forEach()`。 * `map.keys()`:返回遍歷所有鍵的迭代器。 * `map.values()`:返回遍歷所有值的迭代器。 * `map.entries()`:返回遍歷所有項的迭代器,就像`map[Symbol.iterator]()`。實際上,它們就是同一個方法,不同名字。 還有什么要抱怨的?以下是我覺得會有用而ES6還沒提供的特性: * 鍵不存在時返回的默認值,類似 Python 中的`collections.defaultdict`。 * 一個可以叫`Map.fromObject(obj)`的輔助函數,以便更方便地用構造對象的語法來寫出一個`Map`。 同樣,這些特性也是很容易加上的。 到這里,還記不記得,開篇時我提到過運行于瀏覽器對語言特性設計的特殊影響?現在要好好談一談這個問題了。我已經有了三個例子,以下是前兩個。 ## JS是不同的,第一部分:沒有哈希代碼的哈希表? 到目前為止,據我所知,ES6的集合類完全不支持下述這種有用的特性。 比如說,我們有若干 URL 對象組成的Set: ~~~ var urls = new Set; urls.add(new URL(location.href)); // 兩個 URL 對象。 urls.add(new URL(location.href)); // 它們一樣么? alert(urls.size); // 2 ~~~ 這兩個 URL 應該按相同處理,畢竟它們有完全一樣的屬性。但在JavaScript中,它們是各自獨立、互不相同的,并且,絕對沒有辦法來重載相等運算符。 其它一些語言就支持這一特性。在Java, Python, Ruby中,每個類都可以重載它的相等運算符;Scheme的許多實現中,每個哈希表可以使用不同的相等關系。C++則兩者都支持。 但是,所有這些機制都需要編寫者自行實現一個哈希函數并暴露出系統默認的哈希函數。在JS中,因為不得不考慮其它語言不必擔心的互用性和安全性,委員會選擇了不暴露——至少目前仍如此。 ## JS是不同的,第二部分:意料之外的可預測性 你多半覺得一臺計算機具有確定性行為是理所應當的,但當我告訴別人遍歷Map或Set的順序就是其中元素的插入順序時,他們總是很驚奇。沒錯,它就是確定的。 我們已經習慣了哈希表某些方面任性的行為,我們學會了接受它。不過,總有一些足夠好的理由讓我們希望嘗試避免這種不確定性。2012年我寫過: > * 有證據表明,部分程序員一開始會覺得遍歷順序的不確定性是令人驚奇又困惑的。[1](http://stackoverflow.com/questions/2453624/unsort-hashtable)?[2](http://stackoverflow.com/questions/1872329/storing-python-dictionary-entries-in-the-order-they-are-pushed)[3](https://groups.google.com/group/comp.lang.python/browse_thread/thread/15f3b4a5cd6221b1/1b6621daf5d78d73)?[4](http://bytes.com/topic/c-sharp/answers/439203-hashtable-items-order)?[5](http://stackoverflow.com/questions/1419708/how-to-keep-the-order-of-elements-in-hashtable)?[6](http://stackoverflow.com/questions/7105540/hashtable-values-reordered) > * ECMAScript中沒有明確規定遍歷屬性的順序,但為了兼容互聯網現狀,幾乎所有主流實現都不得不將其定義為插入順序。因此,有人擔心,假如TC39不確立一個確定的遍歷順序,“互聯網社區也會在自行發展中替我們決定。”?[7](https://mail.mozilla.org/pipermail/es-discuss/2012-February/020576.html) > * 自定義哈希表的遍歷順序會暴露一些哈希對象的代碼,繼而引發關于哈希函數實現的一些惱人的安全問題。例如,暴露出的代碼絕不能獲知一個對象的地址。(向不受信任的ES代碼透露對象地址而對其自身隱藏,將是互聯網的一大安全漏洞。) 在2012年2月以上種種意見被提出時,我是[支持不確定遍歷序](https://mail.mozilla.org/pipermail/es-discuss/2012-February/020541.html)的。然后,我決定用實驗證明,保存插入序將過度降低哈希表的效率。我寫了一個C++的小型基準測試,結果卻[令我驚奇地恰恰相反](https://wiki.mozilla.org/User%3aJorend/Deterministic_hash_tables)。 這就是我們最終為JS設計了按插入序遍歷的哈希表的過程。 ## 推薦使用弱集合的重要原因 [上篇文章](http://www.infoq.com/cn/articles/es6-in-depth-symbols)我們討論了一個JS動畫庫相關的例子。我們試著要為每個DOM對象設置一個布爾值類型的標識屬性,就像這樣: ~~~ if (element.isMoving) { smoothAnimations(element); } element.isMoving = true; ~~~ 不幸的是,這樣給一個DOM對象增加屬性不是個好主意。原因我們上次已經解釋過了。 上次的文章里,我們接著展示了用Symbol解決這個問題的方法。但是,可以用集合來實現同樣的效果么?也許看上去會像這樣: ~~~ if (movingSet.has(element)) { smoothAnimations(element); } movingSet.add(element); ~~~ 這只有一個壞處。Map和Set都為內部的每個鍵或值保持了強引用,也就是說,如果一個DOM元素被移除了,回收機制無法取回它占用的內存,除非`movingSet`中也刪除了它。在最理想的情況下,庫在善后工作上對使用者都有復雜的要求,所以,這很可能引發內存泄露。 ES6給了我們一個驚喜的解決方案:用`WeakSet`而非`Set`。和內存泄露說再見吧! 也 就是說,這個特定情景下的問題可以用弱集合(weak collection)或Symbol兩種方法解決。哪個更好呢?不幸的是,完整地討論利弊取舍會把這篇文章拖得有些長。簡而言之,如果能在整個網頁的生 命周期內使用同一個Symbol,那就沒什么問題;如果不得不使用一堆臨時的Symbol,那就危險了,是時候考慮WeakMap來避免內存泄露了。 ## WeakMap和WeakSet WeakMap和WeakSet被設計來完成與Map、Set幾乎一樣的行為,除了以下一些限制: * WeakMap只支持new、has、get、set 和delete。 * WeakSet只支持new、has、add和delete。 * WeakSet的值和WeakMap的鍵必須是對象。 還要注意,這兩種弱集合都不可迭代,除非專門查詢或給出你感興趣的鍵,否則不能獲得一個弱集合中的項。 這些小心設計的限制讓垃圾回收機制能回收仍在使用中的弱集合里的無效對象。這效果類似于弱引用或弱鍵字典,但ES6的弱集合可以在**不暴露腳本中正在垃圾回收**的前提下得到垃圾回收的效益。 ## JS是不同的,第三部分:隱藏垃圾回收的不確定性 弱集合實際上是用?[ephemeron 表](http://www.jucs.org/jucs_14_21/eliminating_cycles_in_weak/jucs_14_21_3481_3497_barros.pdf)實現的。 簡單說,一個WeakSet并不對其中對象保持強引用。當WeakSet中的一個對象被回收時,它會簡單地被從WeakSet中移除。WeakMap也類似地不為它的鍵保持強引用。如果一個鍵仍被使用,相應的值也就仍被使用。 為什么要接受這些限制呢?為什么不直接在JS中引入弱引用呢? 再 次地,這是因為標準委員會很不愿意向腳本暴露未定義行為。孱弱的跨瀏覽器兼容性是互聯網發展的痛苦之源。弱引用暴露了底層垃圾回收的實現細節——這正是與 平臺相關的一個未定義行為。應用當然不應該依賴平臺相關的細節,但弱引用使我們難于精確了解自己對測試使用的瀏覽器的依賴程度。這是件很不講道理的事情。 相比之下,ES6的弱集合只包含了一套有限的特性,但它們相當牢靠。一個鍵或值被回收從不會被觀測到,所以應用將不會依賴于其行為,即使只是緣于意外。 這是針對互聯網的特殊考量引發了一個驚人的設計、進而使JS成為一門更好語言的一個例子。 ## 什么時候可以用上這些集合呢? 總計四種集合類在Firefox、Chrome、Microsoft Edge、Safari中都已實現,要支持舊瀏覽器則需要[ES6 - Collections](https://github.com/WebReflection/es6-collections)?之類來補全。 Firefox中的WeakMap 最初由 Andreas Gal 實現,他后來當了一段時間Mozilla的CTO。Tom Schuster實現了WeakSet,我實現了Map和Set。感謝Tooru Fujisawa貢獻的幾個相關補丁。 下周開始是深入淺出ES6系列的兩周暑假。這個系列已經覆蓋了很多內容,不過幾個ES6的最強特性還沒涉及,敬請期待下次的新主題。
                  <ruby id="bdb3f"></ruby>

                  <p id="bdb3f"><cite id="bdb3f"></cite></p>

                    <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
                      <p id="bdb3f"><cite id="bdb3f"></cite></p>

                        <pre id="bdb3f"></pre>
                        <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

                        <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
                        <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

                        <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                              <ruby id="bdb3f"></ruby>

                              哎呀哎呀视频在线观看