es6入門7--Set Map資料結構
本文作為ES6入門第十三章的學習整理筆記,可能會包含少部分個人的理解推測,若想閱讀更詳細的介紹,還請閱讀原文ES6入門
一、set資料結構
1.set不接受重複值
ES6新增了Set建構函式用於建立set資料結構,這種結構類似於陣列,但有很大的一個區別就是,set資料結構不接受重複值,每個值都是唯一的。
我們可以通過Set建構函式快速建立一個set資料結構,順便列印看看究竟長什麼樣:
let s = new Set(); console.dir(s);
那麼可以看到,set例項具有一個size屬性,因為我們還未給此結構新增值,所以是0,類似於陣列的length屬性。
set例項還有很多方法,例如add新增,clear清除,還有在陣列拓展中已經介紹過的keys,values等比較熟悉的方法,這些後面具體再說。
我們嘗試在new命令時直接初始化值:
let s = new Set([1,2,1,3]);
可以看到,儘管我添加了兩個數字1,最終的set例項結構中只有一個不重複的1,這是因為set不接受重複的值,自帶去重效果。
你可能看過以下陣列去重的快捷方法,正式利用的set的這一特點:
// 陣列去重 [...new Set([1, 1, 2, 3, 4, 4])]; Array.from(new Set([1, 1, 2, 3, 4, 4]));
2.set例項的增刪改查方法
add方法:新增某個值,返回新增值後的set解構,類似陣列的push,後新增的元素在set解構後面。
let s = new Set(); s.add(1).add(2);
has方法:查詢set解構是否包含某值,返回一個布林值。
s.has(1); //true s.has(3); //false
delete方法:刪除某個值,返回一個布林值對應是否刪除成功。
s.delete(1);//true s.delete(1);//false
clear方法:清除整個set解構,無返回值。
s.clear();
3.set的遍歷方法
keys方法:遍歷元素的鍵名
values方法:遍歷元素的鍵值
entries方法:遍歷元素的鍵值對
forEach方法:用的賊多,回撥函式遍歷每個元素
在陣列拓展這一章節中也有介紹這三個方法,這裡就簡單說下;三個方法都是結合for...of迴圈使用,分別遍歷元素的key,value與key/value組合。
let s = new Set([{a:1}, {b:2}, {c:3}]); for (let item of s.keys()) { console.log(item);// {a:1}, {b:2}, {c:3} }; for (let item of s.values()) { console.log(item);// {a:1}, {b:2}, {c:3} }; for (let item of s.entries()) { console.log(item);// [{a:1},{a:1}],[{b:2},{b:2}],[{c:3},{c:3}] };
通過上述程式碼中的輸出可以瞭解到,keys方法與values方法執行完全相同,這是因為set解構沒有key名導致,key名與value相同;而entries方法每次返回的是一個包含了key與value的陣列。
當我們想遍歷出set解構的每個元素理論上使用values方法,有趣的是set解構的預設遍歷器剛好與values相等,所以我們甚至能省略掉values方法直接遍歷解構中的每個元素。
let s = new Set([1, 2, 3]); Set.prototype[Symbol.iterator] === Set.prototype.values; //true //省略values方法 for(let item of s){ console.log(item);//1 2 3 };
與陣列中使用這三個方法的區別在於,陣列中的keys遍歷的是元素的下標,values相同,entries是下標和元素組成鍵值對,且不是陣列。
當我們使用forEach遍歷set結構資料時,回撥引數三個引數的前兩個完全相同,這也是因為key名與key值相同的緣故,這點需要注意。
let s = new Set([1, 2, 3]); s.forEach((val,key) => console.log(val,key))//1 1,2 2,3 3
4.set解構的作用
a.陣列去重,主要利用了set不接受重複值做引數的特點。
b.set結構實現並集,簡單點說,就是把兩個set重複項去掉,原理還是利用set不接受重複項
let a = new Set([1, 2, 3]); let b = new Set([2, 3, 4]); let s1 = new Set([...a, ...b]); //set {1,2,3,4}
c.set結構實現交集,原理是利用了set例項的has方法
let s2 = new Set([...a].filter(x => b.has(x)))//set {2,3}
d.set結構實現差集,同理利用了has方法
let s3 = new Set([...a].filter(x => !b.has(x)))//set {1}
你的直覺是不是這裡應該是{1,4},這裡的差集其實是a裡面有且b裡面沒有的元素,而不是ab互相沒有。
二、WeakSet結構
WeakSet資料結構與Set類似,也不接受重複的值,但也有三點不同,一是WeakSet解構的成員只能是物件,二是WeakSet中的物件都是弱引用,三是WeakSet無法遍歷。
1.WeakSet成員只能是物件
let s = new WeakSet(); s.add([{a:1},{b:2}]); console.dir(s); s.add(1);//報錯 Invalid value used in weak set
建立WeakSet 結構可通過new命令完成,WeakSet 接受任何含有Iterable介面的物件作為引數。可以看到當我們add非物件元素,該操作報錯,但是add新增物件沒問題。
那麼我們看這段程式碼,為什麼報錯了:
let s = new WeakSet([1,2,3]);
我在前面你說了,WeakSet的每個成員必須是物件,前面我們使用的是add方法,每次新增都是一個成員,這是直接使用new初始化,雖然傳遞的引數是陣列,但本質上等同於:
let s = new WeakSet(); s.add(1).add(2).add(3);
所以我們需要保證陣列中的每個元素也是物件,這樣就不會報錯了:
let s = new WeakSet([{a:1},{b:2}]);
其次可以看到WeakSet方法並不多,add,has,delete三個,用法和set相同,這裡就不重複介紹了。
2.WeakSet結構成員均為弱引用
我們都知道,當一個物件不被任何地方引用,垃圾回收機制就會釋放掉這個物件所佔用記憶體。我們在前面說WeakSet的成員都是物件,但是垃圾回收機制不考慮WeakSet的引用。
說直白點,現在物件a被A和WeakSet同時引用,A不再引用了垃圾回收機制就直接釋放了,完全不管WeakSet還在引用它。
也正是因為WeakSet成員是弱引用的原因,我們無法保證什麼時候成員就被釋放了,所以WeakSet沒有size屬性,也不可遍歷。
三、map資料結構
1.基本用法與增刪改查方法
傳統意義上的物件都是鍵值對組成的集合,鍵為字串,值為一個物件,我們是無法使用物件作為鍵的。
但Map打破了這個規則,我們可以通過Map建立鍵值都是物件的資料結構,這樣鍵不再是作為儲存值的存在,在遍歷時,鍵值都可以是有效的物件。
let m = new Map(); console.dir(m);
從上圖中,可以看到百分之80的方法與Set資料結構完全相同,只是多了一個set方法和get方法。
set(key,value)方法:按照key/value新增成員,返回Map結構,支援鏈式寫法;如果key已存在,則覆蓋。
get(key)方法:按照key查詢返回對應的value,如果未找到,返回undefined。
has(key)方法:查詢是否包含某個key,返回一個布林值。
delete(key)方法:刪除對應的key,返回一個布林值,表示是否成功刪除。
clear()方法:清空整個Map資料結構。
let m = new Map(); let o = {name:'echo'}; m.set(o,{age:26}); m.get(o);//{age:26} m.has(o);//true m.delete(o);//true m.has(o);//false
那麼在上述程式碼中,我們為map資料結構添加了一個key為{name:'echo'}值為{age:26}的成員。
同時我們可以通過get指定的key訪問到對應的value,delete還是一樣返回是否刪除成功,has依舊是判斷該資料結構是否含有此成員。
新增成員當然不要求通過set,在new命令執行時,我們可以以一個數組的形式傳遞需要新增的成員。
let m = new Map([ ['name', '聽風是風'], ['age', 26] ]); m.has('name') //true m.get('name') //聽風是風 m.has('age') //true m.get('age') //26
其實初次看到這我是有點懵逼的,為什麼我一個數組成員的兩個元素,成了Map資料結構中一個成員的key與value。其實這個不難理解,它等同於以下的執行:
let arr = [ ['name', '聽風是風'], ['age', 26] ]; let m = new Map(); arr.forEach(([key, value]) => m.set(key, value))
陣列每個元素又是一個雙元素陣列,前者作為map的key,後者作為map的value
需要注意的是,map資料結構同樣不接受重複的值作為成員,這裡的重複是指key名相同,如果相同,後者會覆蓋前者:
const m = new Map([ ['name', 1], ['name', 2] ]); console.log(m);//key:name value:2
除此之外,當我們map的key是物件時,需要注意物件引用的問題:
let o = {name:1}; let m = new Map(); m.set(o,2) console.log(m.get(o));//2 m.set({name:1},2) console.log(m.get({name:1}));//undefined
在上述程式碼中,如果我們直接將{name:1}作為key用於存值,在set執行時,無法拿到對應的value,這是因為物件儘管寫法相同,但仍然是完全不同的兩個東西;
所以在需要將物件做key時,請將此物件賦予一個變數,利用此變數作為key進行儲存,在讀取時再次讀取這個變數,就可以避免這個問題了。
其實說到這裡,關於map的key,其實是跟記憶體地址相關。如果key是一個簡單資料型別,那麼只要兩個key完全相等,就視為一個key,且後者覆蓋前者,如果不相等,則反之。
如果key是一個物件,想正確的存取,請將物件賦予給一個變數再做set操作。否則會因為引用地址問題無法訪問到你已經新增的key。
2.Map資料結構的遍歷方法
keys()方法:遍歷並返回鍵名
let m = new Map([ ['name', '聽風是風'], ['age', 26] ]); for(let key of m.keys()){ console.log(key);//name age };
values()方法:遍歷並返回鍵值
for(let value of m.values()){ console.log(value);//聽風是風 26 };
entries()方法:遍歷返回所有成員,注意,我沒說這裡是返回鍵值對
for(let item of m.entries()){ console.log(item); };
如上圖,返回兩個陣列,每個陣列分別包含了key和value,所以如果我們想直接訪問key,value,應該這麼寫:
for(let [key,value] of m.entries()){ console.log(key,value);//name 聽風是風,age 26 };
還記得在介紹Set資料結構是,我們說Set的預設遍歷器介面等於values方法,所以我們可以簡寫遍歷,比較好運的是,Map資料結構的預設遍歷器介面等於entries方法,所以我們還可以繼續簡寫:
m[Symbol.iterator] === m.entries; //true for (let [key, value] of m) { console.log(key, value);//name 聽風是風,age 26 };
forEach方法,通過回撥引數也可以方便的訪問到Map結構的key與value
m.forEach((value, key, m) => console.log(value, key));//聽風是風 name,26 "age"
四、WeakMap資料結構
WeakMap與Map結構類似,但也有兩點不同,一是WeakMap成員的key只接受物件:
let m = new WeakMap(); m.set('name',1);//報錯
二是WeakMap的鍵名所引用物件為弱引用,也就是不計入垃圾回收機制,這點與WeakSet一致。
let m = new WeakMap(); let ele = document.querySelector("#div"); m.set(ele, '這是一個div元素'); m.get(ele); //這是一個div元素
在上述程式碼中,我們先是將獲取的dom存在了ele變數中,此時對於div dom的引用次數是1次。
然後我們又將ele作為key,為這個ele添加了一些說明,照理說,div dom此時又被WeakMap結構引用,所以div引用次數是2次。
但由於WeakMap的key名物件是弱引用,所以這裡div一共的引用此事還是1次。當我們讓ele不再引用div元素時,垃圾回收機制不會考慮WeakMap對於div的引用,而是直接釋放,這點其實與WeakSet是保持一致的。
強調一點的是,WeakMap弱引用的是key,而不是value,這裡有個例子:
let m = new WeakMap(); let key = {}; let value = {a: 1}; m.set(key, value); value = null; m.get(key) //{a:1}
即便我們將WeakMap中value所引用的物件釋放,其實垃圾回收機制還是將WeakMap的引用計為1次,所以還能正常讀取到。
因為key是弱引用的緣故,所以與WeakSet一樣,不存在遍歷方法。
WeakMap結構最大的一個用處就是用於儲存dom,這樣dom元素被刪除也不會造成記憶體洩漏問題:
let ele = document.getElementById('logo'); let fn = function () { console.log(1) }; let m = new WeakMap(); //將dom元素與需要執行的函式作為WeakMap結構的key與value m.set(ele, fn); //為dom元素增加監聽 ele.addEventListener('click', function () { //執行監聽函式 m.get(ele)(); }, false);
關於WeakMap這裡就不多做介紹了,至少我目前開發基本使用不到.....
不只是是WeakMap,Set與Map的使用概率基本很低,這裡就純做一個整理了,日後萬一用到,或者說使用逐漸普及,也方便查詢。
那麼就寫到這裡