ES6新式集合類解析——Map、Set、WeakMap和WeakSet
譯文簡介
多數(shù)主流編程語言都提供了若干種類型的數(shù)據(jù)集合支持。例如,Python提供了列表、元組和詞典;Java語言中具有列表、集合、映射和隊(duì)列;Ruby提供了哈希表和數(shù)組。然而,JavaScript,直到現(xiàn)在,僅提供了對數(shù)組的支持。如你所知,對象和數(shù)組一直成為JavaScript編程的主力。目前,ES6新引入了四種新的數(shù)據(jù)結(jié)構(gòu),它們分別是:映射(Map)、集合(Set)、弱集合(WeakSet)和弱映射(WeakMap)。在本文中,讓我們一起學(xué)習(xí)這四種新增添的集合各自的優(yōu)勢吧。
ES5中HashMap的不足分析
散列、詞典和哈希表等是各種編程語言用來存儲鍵/值對這一類數(shù)據(jù)的數(shù)據(jù)結(jié)構(gòu)。而且,通常情況下這些數(shù)據(jù)結(jié)構(gòu)都進(jìn)行了快速檢索方面的優(yōu)化處理。
在ES5,盡管可以使用JavaScript對象(它們其實(shí)就是一些帶有鍵和值的屬性的任意集合)來模擬哈希表,但還是存儲如下幾個不足的地方。
缺點(diǎn)之1:在ES5中鍵必須是字符串
JavaScript對象中的屬性鍵部分必須是字符串類型,這就限制了它們作為不同數(shù)據(jù)類型的鍵/值對的集合的能力。當(dāng)然,你可以強(qiáng)制把其他數(shù)據(jù)類型轉(zhuǎn)換為字符串,但是這無疑會增加額外的系統(tǒng)負(fù)擔(dān)。
缺點(diǎn)之2:對象天生就不可迭代
對象并不是設(shè)計(jì)作為集合使用的;因此,沒有有效的方法來確定一個對象到底有多少屬性(例如,Object.keys的效率就很低)。當(dāng)您遍歷某對象的屬性時,同時你也獲得了該對象的原型屬性。誠然,你可以將可迭代的屬性添加到所有對象,但并不是所有的對象都為了用作集合而設(shè)計(jì)的。例如,您可以使用 for …in循環(huán)和hasOwnProperty()方法,但這只不是一種變通的解決方法而已。當(dāng)您遍歷對象的屬性時,這些屬性不必以與插入它們時相同的順序進(jìn)行檢索。
缺點(diǎn)之3:與內(nèi)置方法命名可能發(fā)生沖突
對象都具有內(nèi)置的方法,如constructor、toString和valueOf。如果其中之一作為一個屬性添加到對象上,這很可能會導(dǎo)致沖突。當(dāng)然,您可以使用Object.create(null)來創(chuàng)建一個空的對象(它不繼承自object.prototype);但是,這仍然也只是一種變通的方法而已。
***的ES6提供了新的集合數(shù)據(jù)類型;因此,再也不需要使用對象來進(jìn)行集合模擬并不得不忍受其帶來的不足了。
使用ES6中的MapCollection
映射是我們要學(xué)習(xí)的***個數(shù)據(jù)結(jié)構(gòu)(或者說“集合”)。映射是任何類型的值/鍵對的集合。你可以很容易地創(chuàng)建新的映射、添加/刪除值、遍歷鍵/值以及有效地確定其大小。下面是這種數(shù)據(jù)結(jié)構(gòu)提供的幾個關(guān)鍵的方法:
創(chuàng)建映射并使用其常用的方法
請參考下圖中的代碼來學(xué)習(xí)如何創(chuàng)建一個映射和映射中提供的常用方法的用法:
使用ES6中的SetCollection
集合是值的有序列表,其中的值是不允許重復(fù)的。集合不是像數(shù)組一樣進(jìn)行索引,而是通過鍵來訪問的。事實(shí)上,集合早已經(jīng)存在于像Java、Ruby、Python及許多其他語言之中。ES6中的集合和其他語言中的集合的一個重要區(qū)別是,ES6中的集合是有順序的(在許多其他的語言卻不是這樣)。下圖給出集合的幾個關(guān)鍵方法的用法舉例:
弱集合、內(nèi)存與垃圾回收
JavaScript垃圾回收是一種內(nèi)存管理技術(shù)。在這種技術(shù)中,不再被引用的對象會被自動刪除,而與其相關(guān)的資源也會被一同回收。
Map和Set中對象的引用都是強(qiáng)類型化的,并不會允許垃圾回收。這樣一來,如果Map和Set中引用了不再需要的大型對象,如已經(jīng)從DOM樹中刪除的DOM元素,那么其回收代價是昂貴的。
為了解決這個問題,ES6還引入了另外兩種新的數(shù)據(jù)結(jié)構(gòu),即稱為WeakMap和WeakSet的弱集合。這些集合之所以是“弱的”,是因?yàn)樗鼈冊试S從內(nèi)存中清除不再需要的被這些集合所引用的對象。
使用ES6中的WeakMap
WeakMap是我們要介紹的第三個新的ES6集合。WeakMap類似于通常的映射(Map),盡管它提供了更少的方法支持以及存在與前面提到的與垃圾回收有關(guān)的不同處理方案。
請參考下圖中的代碼來了解這種數(shù)據(jù)結(jié)構(gòu)的創(chuàng)建及其少數(shù)的幾個方法的使用:
用例分析
網(wǎng)址http://stackoverflow.com/questions/29413222/what-are-the-actual-uses-of-es6-weakmap處提供了幾個很受歡迎的有關(guān)WeakMap的實(shí)例。它們可以用來使一個對象的私有數(shù)據(jù)“私有”,也可以用來跟蹤DOM節(jié)點(diǎn)/對象。
私有數(shù)據(jù)使用舉例
下面的示例是由JavaScript專家Nicholas C. Zakas先生提供的。
在這里,使用WeakMap簡化了保持對象的私有數(shù)據(jù)真正“私有”的過程。你可以引用Person對象,但在沒有特定的Person實(shí)例的情況下對privateDataWeakMap的訪問是不允許的。
DOM節(jié)點(diǎn)使用舉例
谷歌聚合項(xiàng)目(https://github.com/Polymer)中就在一段稱為PositionWalker的代碼中使用了WeakMaps。其中的PositionWalker方法用于跟蹤一棵DOM子樹中的位置,這個“位置”對應(yīng)于當(dāng)前節(jié)點(diǎn)和距離該節(jié)點(diǎn)的偏移量。該方法中使用WeakMap來跟蹤DOM節(jié)點(diǎn)的編輯、刪除和變化等。
使用ES6中的WeakSet
WeakSet是集合(Set)的集合,當(dāng)不再需要該集合中的元素引用時可以把它們進(jìn)行垃圾收集。但是,WeakSet不允許迭代。有關(guān)它們的用法相當(dāng)有限(至少到目前還是很少見)。大多數(shù)的早期采用者都說,WeakSet可用于標(biāo)記對象而不是改變它們。ES6-Features.org網(wǎng)站(http://es6-features.org/)上提供了一個從WeakSet中添加和刪除元素的例子,目的是為了記錄是否對象已被標(biāo)記過,請參考下圖中代碼:
能否映射一切?
映射和集合都只是比較新穎的鍵/值對的集合。也就是說,在許多情況下JavaScript對象仍然可以用作集合。無需切換到的這些新的集合,除非形勢需要這樣做。
MDN網(wǎng)站(https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map)提供了一個問題列表,你可以參考確定何時使用對象或何時使用鍵/值對的集合:
1.鍵是否在運(yùn)行時前一直是未知的?您需要動態(tài)查找這些鍵嗎?
2.是否所有的值都具有相同的類型并可以互換使用?
3.你真正需要不是字符串的鍵嗎?
4.你經(jīng)常添加或刪除鍵-值對嗎?
5.你是否有任意(很容易改變)數(shù)目的鍵-值對?
6.你的集合能夠迭代嗎?
小結(jié)
以前JavaScript集合被相當(dāng)有限地使用,但ES6改變了這一點(diǎn)。這些新的集合將為JavaScript語言添加強(qiáng)大功能和靈活性,從而簡化JavaScript程序員的開發(fā)任務(wù)。