Set和Map資料結構。

Lessong發表於2019-02-16

ES6提供了新的資料結構Set,它類似與陣列,但是成員值都是唯一的,沒有重複的值。 Set本身是一個建構函式,用來生成Set資料結構。

const s = new Set();
[2,3,5,4,5,2,2].forEach(x=>s.add(x));
for(let i of s){
    console.log(i);//2 3 5 4
}

上面程式碼通過add()方法向Set結構加入成員,結果表明Set結構不會新增重複的值。
Set函式可以接受一個陣列作為引數,用來初始化。

//例子1
const set = new Set([1,2,3,4,4]);
[...set];//[1,2,3,4]
//例子2
const items = new Set([1,2,3,4,5,5,5,5]);
items.size//5
//例子3
const set = new Set(document.querySelectorAll(`div`));

上面程式碼中,例一和例二都是Set函式接受陣列作為引數,例三是接受類似陣列的物件作為引數。
上面程式碼也展示了一種去除陣列重複成員的方法。

//去除陣列的重複成員
[...new Set(array)]
//去除字串裡面的重複字元
[...new Set(`ababbc`)].join(``);//`abc`
//下面程式碼向Set例項新增了兩個NaN,但是隻能加入一個。這表明在Set內部兩個NaN是相等。
let set = new Set();
let a = NaN;
let b = NaN;
set.add(a);
set.add(b);
set // Set {NaN}
//另外,兩個物件總是不相等的。
let set = new Set();
set.add({});
set.size // 1
set.add({});
set.size // 2

Set例項的屬性和方法

Set結構的例項有以下屬性。

-Set.prototype.constructor:建構函式,預設就是set函式。
-Set.prototype.size:返回Set例項的成員總數。

Set例項的方法分為兩大類:操作方法和遍歷方法。下面先介紹四個操作方法。

-add(value):新增某個值,返回Set結構本身。
-delete(value):刪除某個值,返回一個布林值,表示刪除是否成功。
-has(value):返回一個布林值,表示該值是否為Set的成員。
-clear();清除所有成員,沒有返回值。

下面這些屬性和方法的示例如下:

s.add(1).add(2).add(2);
//注意2被加入了兩次
s.size//2
s.has(1)//true
s.has(2)//true
s.has(3)//false
s.delete(2);
s.has(2)//false

下面是一個對比,看看在判斷是否包括一個鍵上面,Object結構和Set結構的寫法不同。

//物件的寫法
const properties ={
    `width`:1,
    `height`:1
};
if(properties[someName]){
    //do something
}
//Set的寫法
const properties = new Set();
properties.add(`width`);
properties.add(`height`);
if(properties.has(someName)){
    //do something
}

Array.from方法可以將Set結構轉為陣列。

const items = new Set([1,2,3,4,5]);
const array = Array.from(items);

這就提供了去除陣列重複成員的另一種方法。

function dedupe(array){
    return Array.from(new Set(array));
};
dedupe([1,1,2,3])//[1,2,3]

遍歷操作

Set 結構的例項有四個遍歷方法,可以用於遍歷成員。

-keys():返回鍵名的遍歷器。
-values():返回鍵值的遍歷器。
-entries():返回鍵值對的遍歷器。
-forEach():使用回撥函式遍歷每個成員。

(1)keys(),value(),entries()
keys方法、values方法、entries方法返回的都是遍歷器物件。由於Set結構沒有鍵名,只有鍵值所以keys方法和values方法的行為完全一致。

let set = new Set([`red`,`green`,`blue`]);
for(let item of set.keys){
    console.log(items);//red,grren,blue
}
for(let item of set.values){
    console.log(item);//red,green,blue
}
for(let item of set.entries){
    console.log(item)
}
//[`red`,`red`][`green`,`green`][`blue`,`blue`]

上面程式碼中,entries方法返回的遍歷器,同時包括鍵名和鍵值,所以每次輸出一個陣列,它的兩個成員完全相等。
Set結構的例項預設可遍歷,它的預設遍歷器生成函式就是它的values方法。

Set.prototype[Symbol.iterator] === Set.prototype.values

這就意味著,可以省略values方法,直接用for…of迴圈遍歷Set。

let set = new Set([`red`,`green`,`blue`]);
for(let x of set){
    console.log(x);
}
//red green blue

(2)forEach()
Set結構的例項與陣列一樣,也擁有forEach方法,用於對每個成員執行某種操作,沒有返回值。

let set = new Set([1,4,9]);
set.forEach((value,key)=>console.log(key+`:`+value))
//1:1 4:4 9:9

上面程式碼說明,foreach方法的引數就是一個處理函式。該函式的引數與陣列的forEach一致,依次為鍵值、鍵名、集合本身。這裡需要注意,Set結構的鍵名就是鍵值,因此第一個引數與第二個引數的值永遠都是一樣的。另外,forEach方法還可以有第二個引數,表示繫結處理函式內部的this物件。
(3)遍歷的應用
擴充套件運算子(…)內部使用for…of迴圈,所以也可以使用Set結構。

let set = new Set([`red`,`green`,`blue`]);
let arr = [...set];
//[`red`,`green`,`blue`]
擴充套件運算子和Set結構相結合,就可以去除陣列的重複成員。
let arr = [3,5,2,2,5,5];
let unique = [...new Set(arr)]
//[3,5,2]

而且,陣列的map和filter方法也可以間接用於Set了。

let set = new Set([1,2,3]);
set = new Set([...set].map(x=x>*2));
//返回set結構:{2,4,6}

let set = new Set([1,2,3,4,5]);
set = new Set([...set].filter(x=>(x%2)==0));
//返回Set結構:{2,4}

因此使用Set可以很容易地實現並集、交集、和差集。

let a = new Set([1,2,3]);
let b = new Set([4,3,2]);
//並集
let union = new Set([...a,...b]);
//Set {1,2,3,4}
//交集
let intersect = new Set([...a].filter(x=>b.has(x)));
//set{2,3}
//差集
let difference = new Set([...a].filter(x=>!b.has(x)));
//Set{1}

Map含義和基本用法

Javascript的物件,本質上是鍵值對的集合,但是傳統上只能用字串當作鍵,這給它的使用帶來了很大的限制。

const data = {};
const element = document.getElementById(`myDiv`);
data[element] = `metadata`;
data[`[object HTMLDivElement]`]//`metadata`

上面程式碼原意是將一個DOM節點作為物件data的鍵,但是由於物件只接受字串作為鍵名,所以element被自動轉為字串[object HTMLDivElement].
為了解決這個問題,ES6提供了Map資料結構。他類似與物件,也是鍵值對的集合,但是鍵的範圍不限於字串,各種型別的值都可以當作鍵。也就是說,Object結構提供了字串-值的對應,Map結構提供了值-值的對應,是一種更完善的Hash結構實現。如果你需要鍵值對的資料結構,Map比Object更合適。

const m = new Map();
const o = {p:`Hello World`};
m.set(o,`content`);
m.get(o)//`content`
m.has(o)//true
m.delete(o)//true
m.has(o)//false

上面程式碼使用Map結構set方法,將物件o當作m的一個鍵,然後又使用get方法讀取這個鍵,接著使用delete方法刪除這個鍵。
上面的例子展示瞭如何向Map新增成員。作為建構函式,Map也可以接受一個陣列作為引數。該陣列的成員是一個個表示鍵值對的陣列。

const map = new Map([
    [`name`,`張三`],
    [`title`,`Author`]
]);
map.size//2
map.has(`name`)//true
map.get(`name`)//張三
map.has(`title`)//true
map.get(`title`)//Author

事實上,不僅僅是陣列,任何具有Iterator介面、每個成員都是一個雙元素的陣列的資料結構都剋以當作Map建構函式的引數。也就是說,Set和Map都可以用來生成新的Map。

const set = new Set([
    [`foo`,1],
    [`bar`,2]
])
const m1 = new Map(set);
m1.get(`foo`)//1

const m2 = new Map([[`baz`,3]]);
const m3 = new Map(m2);
m3.get(`baz`)//3

上面程式碼中,我們分別使用set物件和Map物件,當作Map建構函式的引數,結果都生成了新的Map物件。
如果對同一個鍵多次賦值,後面的值將覆蓋前面的值。

const map = new Map();
map.set(1,`aaa`)
   .set(1,`bbb`)
map,get(1)//`bbb`

上面程式碼對鍵1連續賦值兩次,後一次的值覆蓋前一次的值。
如果讀取一個未知的鍵,則返回undefined。

new Map().get(`dsada`)//undefined.

例項的屬性和操作方法

(1)size屬性
size屬性返回Map結構的成員總數。

const map = new Map()
map.set(`foo`,true)
map.set(`bar`,false)
map.szie//2

(2)set(key,value)
set方法設定鍵名key對應的鍵值為value,然後返回整個map結構。如果key已經有值,則鍵值會被更新,否則就新生成該鍵。

const m = new Map()
m.set(`edition`,6);//鍵是字串
m.set(262,`standard`);//鍵是數值
m.set(undefined,`nah`);//鍵是undefined

set方法返回的是當前的Map物件,因此剋以採用鏈式寫法。

let map = new Map()
    .set(1,`a`)
    .set(2,`b`)
    .set(3,`c`)

(3)get(key)
get方法讀取key對應的鍵值,如果找不到key,返回undefined。

const m = new Map();
const hello = function(){console.log(`hello`);};
m.set(hello,`Hello ES6`)//鍵是函式
m.get(hello)//Hello ES6   

(4)has(key)
has方法返回一個布林值,表示某個鍵是否在當前Map物件之中。

const m = new Map();
m.set(`edition`,6);
m.set(`262`,`standard`);
m.set(undefined,`nah`);
m.has(`edition`)//true

(5)delete(key)
delete方法刪除某個鍵,返回true,如果刪除失敗,返回false。

const m = new Map();
m.set(undefined,`nah`);
m.has(undefined)//true
m.delete(undefined);
m.has(undefined);//false

(6)clear()
clear方法清除所有成員,沒有返回值。

let map = new Map();
map.set(`foo`,true);
map.set(`bar`,false);
map.size//2
map.clear();
map.size//0;

相關文章