1. 程式人生 > >es6入門7--Set Map資料結構

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的使用概率基本很低,這裡就純做一個整理了,日後萬一用到,或者說使用逐漸普及,也方便查詢。

那麼就寫到這裡