ES6新增了兩種基本的原生數據集合:Set
和Map
(加上Array
和Object
如今共有四種),以及由二者衍生出的弱引用集合:WeakSet
和WeakMap
。從某個不無狹隘的角度看(不無狹隘?到底有多狹隘多不狹隘呢?),Set
更爲相似Array
集合的某種提高,而Map
則爲Object
集合的加強,雖然兩類在本質上就不相同。html
其自己是生成Set
實例(數據集合)的構造函數,能夠接受一個數組(或具備iterable
接口的數據結構)做爲參數用來初始化。存儲的結構相似數組,不過成員的值在集合中都是惟一的,不會出現重複值。實際的存儲順序(也是遍歷順序)與插入順序一致,行爲的結果和數組相同。segmentfault
其數據結構中沒有鍵名,但爲了和Map
統一,也可認爲鍵名和健值是同一值(會在遍歷小節中介紹)。內部使用的相等判斷規則,除了認爲NaN
等於NaN
,與全等於一致。利用Set
中沒有重複值的特性,能夠簡單的實現數組去重。最後一點,其實例的字符串標籤爲[Object Set]
——厲害啦,都自立了門戶。數組
let set = new Set([1, 2]); console.log(...set); // 1 2 console.log({}.toString.call(set)); // [Object Set] let o = {}; [...new Set([1, 2, 2])]; // [1, 2] [...new Set([1, NaN, NaN])]; // [1, NaN] [...new Set([1, o, o])]; // [1, o] [...new Set([1, {}, {}])]; // [1, {}, {}] F(1, {}, 1); // [1, {}],可解析帶遍歷器的類數組對象。 function F() { console.log([...new Set(arguments)]); } let o = { length: 0 }; [...new Set(o)]; // 報錯,不能自動解析不帶遍歷器接口的類數組對象。 // 做爲簡單的刪除數組重複值的方法。 removeDuplicateValues([1, 2, 2, 3]); // [1, 2, 3] function removeDuplicateValues(arr) { return [...new Set(arr)]; }
關於相等的小知識
兩值是否相等通常指的是是否全等,裏面有兩個比較特殊的例子:NaN & NaN
和-0 & +0
。在全等中,NaN
不等於NaN
,-0
等於+0
都爲零。但這兩種認定在某些場合中不太接地氣,爲此ES6給出用於判斷兩值是否相等方法Object.is()
中,認定NaN
等於NaN
,-0
不等於+0
。數據結構
0除以1爲正零,0除以負1爲負零,二者在生成上方式上看的確不該該相等。app
非數NaN
是一個不是數字的數字(類型依舊爲數字型),沒有具體的值,所以兩個NaN
是不相等的。不過在用NaN
做爲映射中的鍵時,它應該代指這一類型而不是具體的個體。否者我先設置代碼NaN
指向貂蟬,再設置NaN
指代西施,晚上寬衣解帶後發現僕人竟將兩人同時安置在被窩之中,笑盈盈水靈靈的。這,讓我如何是好!dom
其自己是生成Map
實例(數據集合)的構造函數,能夠接受一個包含鍵值對的數組(或具備iterable
接口的數據結構)做爲參數用來初始化。簡單的說,鍵值對是包含兩元素的數組,前者爲鍵名後者爲鍵值。其存儲的結構相似Object
,不會出現重複的鍵名,之中使用的相等斷定方法與Set
一致。其實例的字符串標籤爲[Object Map]
。函數
其與對象主要有兩點不一樣。一是鍵名,對象的鍵名只能是字符串或Symbol
值,而Map
能夠是任意類型,它提供了更爲完善的值對值的Hash
結構。二是遍歷順序,對象的遍歷順序大體爲先數值再字符串後Symbol
值(會在遍歷小節中介紹),而Map
是簡單的與存儲順序保持一致,這在實際操做中比較有用。優化
let map = new Map([[1, 'one'], [2, 'two']]); console.log(...map); // [1,'one'] [2, 'two'] console.log({}.toString.call(map)); // [Object Map] let o = {}; [...new Map([[o, 1], [o, 2]])]; // [[o, 2]] [...new Map([[{}, 1], [{}, 2]])]; // [[{}, 2], [{}, 2]] [...new Map([[null, 1], [undefined, 2]])]; // [[null, 1], [undefined, 2]] let o = { 0: [1, '1'], 1: [2, '2'], length: 2, [Symbol.iterator]: Array.prototype[Symbol.iterator] }; [...new Map(o)]; // [[1, '1'], [2, '2']]
這裏會將Set
與Map
放置在一塊兒,在操做方法和遍歷方法上進行異同性說明,方便區分和記憶。另外,爲了在方法操做上統一Set
與Map
,如Set
小節中說起的,咱們能夠認爲Set
是鍵名與鍵值爲同一值的存在(JS自己就是這樣作的)。prototype
二者都有的操做方法
判斷(has
),傳入鍵名,返回布爾值。
刪除(delete
),傳入鍵名,有且成功刪除爲true
,不然爲false
。
清空(clear
),無需傳參,沒有返回值。
Set
獨有的操做方法
新增(add
),傳入值,返回實例自己。
沒有相應的獲取方法,由於獲取需傳入的值就是應所傳出的值。
let set = new Set(); set.add(1).add(NaN); // set.size 爲 2。 set.has(NaN); // true set.delete(NaN); // true set.has(NaN); // false set.delete(2); // false set.clear(); console.log(...set); // 它變得一無全部,只剩一具空殼。
Map
獨有的操做方法
新增(set
),傳入鍵名和鍵值,有則更新沒則新增,返回實例自己。
獲取(get
),傳入鍵名,返回相應值沒有則爲undefined
。
let o = [1, 2, 3]; let map = new Map(); map.set(o, 1).set(o, 2); // map.size 爲 1 map.has(o); // true map.get(o); // 2 map.get([1, 2, 3]); // undefined map.delete(o); // true map.clear(); console.log(...map); // 它再次一無全部,只剩愈發飢渴的獸心。
返回鍵名的遍歷器對象(keys
)。
返回鍵值的遍歷器對象(values
)。
返回鍵值對的遍歷器對象(entries
),鍵值對爲[鍵名, 鍵值]
。
遍歷每一個成員(forEach
),使用方式與Array
的方法相同。
由於Set
的鍵名和鍵值相同,因此通常只使用values
方法獲取所有值。而Map
則根據相應需求獲取便可。
let set = new Set([1, 2, 3]); let map = new Map([[1, 'one'], [null, NaN]]); [...set.values()]; // [1, 2, 3] [...set.keys()]; // [1, 2, 3] [...set.entries()]; // [[1, 1], [2, 2], [3, 3]] [...map.values()]; // ['one', NaN] [...map.keys()]; // [1, null] [...map.entries()]; // [[1, 'one'], [null, NaN]]
二者的forEach
方法與數組的不一樣點在於回調函數的第二個參數,前者爲該項的鍵名後者爲該項的序號。
let set = new Set([1, 2, 3]); let map = new Map([[1, 'one'], [null, NaN]]); set.forEach((v, i) => console.log(i)); // 1, 2, 3 map.forEach((v, i) => console.log(i)); // 1, null [1, 2, 3].forEach((v, i) => console.log(i)); // 0, 1, 2
對象屬性的遍歷順序
不一樣遍歷對象的方法面向的數據種類不一樣,但總的說其遍歷順序是這樣的:先找到其中可轉化成數值的屬性並按升序遍歷,再遍歷字符串屬性按加入時間的先後,最後遍歷Symbol
值按加入時間的先後。Map
的遍歷順序即其被插入時的順序,嗯,總有些色咪咪的味道。
let obj = { '0': 0, 1: 1, 'b': 2, 'a': 3, [Symbol(2)]: 4, [Symbol(1)]: 5 }; Reflect.ownKeys(obj); // ["0", "1", "b", "a", Symbol(2), Symbol(1)]
弱引用集合WeakSet
和WeakMap
是由Set
和Map
分別衍生出的,其與本體的異同點一致,所以只對WeakMap
進行說明。
在JS的垃圾回收機制中,對象會在沒有引用時(可意爲使用)被回收。這說明着,若是有個數據集合(數組、對象、Set
或Map
)中包含了某對象(在使用它),那麼在此數據集合被回收以前該對象都不能被回收。這很容易致使內存泄漏(專有名詞,可簡單理解爲內存被沒用的數據佔據)。
弱引用集合的設計目的就是爲了解決這個問題。弱引用顧名思義是指雖然某對象被此集合引用了,但該引用不被引擎保護,不被垃圾回收裝置考慮在內,該回收時就得乖乖的被回收。那些被慣成畸形的傢伙們,要知道,媽媽的懷抱可不是個萬全的地方哦,惟有死神的纔是。
WeakMap
的行爲與Map
除了如下幾點不一樣外,能夠認爲是一致的。 WeakMap
的鍵名只能是對象(不包括null
),否者報錯。若是能放入普通類型,那有什麼意義呢? WeakMap
的鍵名是動態不定的,不知道何時會被回收。鍵名指代的對象被回收後,該項會被自動消除。
由於項數的動態性,因此不能被遍歷(沒有遍歷方法),沒有size
屬性,沒有cealr
方法。
let o = {}; let wm = new WeakMap(); wm.set(1, 1); // 報錯,1 不是對象。 wm.set(o, 1); wm.has(o); // true wm.get(o); // 1 wm.delete(o); // true 'size' in wm; // false 'clear' in wm; // false 'values' in wm; // false
弱引用集合的優勢在於,咱們能夠任意爲其註冊對象,而不用擔憂內存泄漏。典型的應用場景是將DOM
與數據進行綁定。一些要在DOM
中綁定數據的庫中,好比d3
,會直接在DOM
對象上設置屬性進行保存。但在平常組建單頁面程序中的某個階段,想將DOM
與數據聯繫在一塊兒時,咱們顯然會優先選用數據映射的方式。而弱引用集合的出現,更加優化了這種方式。
在下面的示例中,每次點擊請求數據後都會生成幫了相應數據項的li
標籤,並將該標籤與相應的數據進行綁定。在這一系列輪迴存儲綁定中,由於WeakMap
的弱引用特性,咱們不須要關心已經被刪除的DOM
元素。每次只需進行相同的操做,方便安心,省時省力。
<button onclick="requestData()">Request Data</button> <ul id="container"></ul> <script> "use strict"; let wmap = new WeakMap(); let container = document.querySelector('#container'); function requestData() { container.innerHTML = ''; [0, 0, 0].map(() => Math.random()).forEach(d => { let li = document.createElement('li'); li.innerHTML = ` <button onclick="showMes(this)">Show Message</button> <button onclick="deleteItem(this)">Delete Item</button> `; wmap.set(li, d); container.appendChild(li); }); } function showMes(that) { let li = that.parentNode; li.innerHTML = wmap.get(li); } function deleteItem(that) { let li = that.parentNode; container.removeChild(li); } </script>
ES6精華:Symbol
ES6精華:解構賦值
ES6精華:函數擴展
ES6精華:Proxy & Reflect
Iterator:訪問數據集合的統一接口