ES6學習筆記之Set和Map

落木蕭蕭下發表於2018-04-29

Set

基本用法

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

const setArr = new Set();

[1,2,3,4,2,2,3,4].forEach(x => setArr.add(x));

for (let i of setArr) {
  console.log(i);
}
// 1,2,3,4
複製程式碼

Set 函式可以接受一個陣列(或者具有 iterable 介面的其他資料結構)作為引數,用來初始化。 特點:

  1. 以陣列為引數, 可以去重
  2. Set 加入值的時候,不會發生型別轉換,所以4和"4"是兩個不同的值。
//  以陣列為引數
const set = new Set([1, 2, 3, 4, 4, '4'])
[...set]  // [1, 2, 3, 4, '4']
set.size  // 5

// 一個類似陣列的帶 iterable 介面的物件
const set = new Set(document.querySelectorAll('div'))

複製程式碼

Set結構轉換成陣列有兩個簡單的方法

[...set]  // [1, 2, 3, 4, '4']

Array.from(set)  // [1, 2, 3, 4, '4']
複製程式碼

Set 例項的屬性和方法

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

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

Set 例項的方法分為兩大類:操作方法(用於運算元據)和遍歷方法(用於遍歷成員)。

四個操作方法。

  1. add(value):新增某個值,返回 Set 結構本身。
  2. delete(value):刪除某個值,返回一個布林值,表示刪除是否成功。
  3. has(value):返回一個布林值,表示該值是否為Set的成員。
  4. clear():清除所有成員,沒有返回值。
const s = new Set()
s.add(1).add(2).add(2)

s.size // 2

s.has(1) // true
s.has(2) // true
s.has(3) // false

s.delete(2)
s.size  // 1
s.has(2) // false

複製程式碼

四個遍歷方法

  1. keys():返回鍵名的遍歷器
  2. values():返回鍵值的遍歷器
  3. entries():返回鍵值對的遍歷器
  4. forEach():使用回撥函式遍歷每個成員

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

const set = new Set(['a', 'b', 'c']);

for (let item of set.keys()) {
  console.log(item);
}
// a
// b
// c

for (let item of set.values()) {
  console.log(item);
}
// a
// b
// c

for (let item of set.entries()) {
  console.log(item);
}
// ["a", "a"]
// ["b", "b"]
// ["c", "c"]

複製程式碼

物件結構 在使用這個幾個方法的時候類似,但有一定區別

const obj = { 1: 'a', 2: 'b', 3: 'c' }
for (let item of Object.keys(obj)) {
  console.log(item);
}
// 1
// 2
// 3

for (let item of Object.values(obj)) {
  console.log(item);
}
// a
// b
// c

for (let item of Object.entries(obj)) {
  console.log(item);
}
// [1, "a"]
// [2, "b"]
// [3, "c"]
複製程式碼

Set 結構的例項預設可遍歷,它的預設遍歷器生成函式就是它的values方法。
這意味著,可以省略values方法,直接用for...of迴圈遍歷 Set

for (let i of set) {
  console.log(i)
}
// a
// b
// c

複製程式碼

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

set.forEach((value, key) => console.log(key + ' : ' + value))
// a: a
// b: b
// c: c
複製程式碼

遍歷的應用

  1. 去重
  2. 實現並集(Union)、交集(Intersect)和差集(Difference)

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([...union].filter(x => !intersect.has(x)));
// Set {1}
複製程式碼

如果想在遍歷操作中,同步改變原來的 Set 結構,目前沒有直接的方法,但有兩種變通方法。

// 方法一
let set = new Set([1, 2, 3]);
set = new Set([...set].map(val => val * 2));
// set的值是2, 4, 6

// 方法二
let set = new Set([1, 2, 3]);
set = new Set(Array.from(set, val => val * 2));
// set的值是2, 4, 6
複製程式碼

WeakSet

WeakSet 結構與 Set 類似,也是不重複的值的集合。但是,它與 Set 有兩個區別。

(1):WeakSet 可以接受陣列和類似陣列的物件作為引數。該陣列的所有成員都會自動成為WeakSet的例項物件的成員。陣列成員只能是物件,不能是其他型別的值。否則報錯。

const a = [[1, 2], [3, 4], {a: 1}]
const ws = new WeakSet(a)
// WeakSet {[1, 2], [3, 4]}

const b = [1, 2, [1,2]]
new WeakSet(b)  // Uncaught TypeError: Invalid value used in weak set
複製程式碼

(2):WeakSet 中的物件都是弱引用,即垃圾回收機制不考慮 WeakSet 對該物件的引用,也就是說,如果其他物件都不再引用該物件,那麼垃圾回收機制會自動回收該物件所佔用的記憶體,不考慮該物件還存在於 WeakSet 之中。

Map

含義和基本用法

一個常規的物件本質應該是鍵值對的合集(即Hash結構)。它的鍵應該是一個字串。 但是有時候需要使用其他型別比如物件來做 鍵值對的 鍵。 於是就有了Map結構

const data = {};
const element = document.getElementsByTagName('div')

data[element] = 'div';
data[element]  //  "div"
data['[object HTMLCollection]'] // "div"

// {[object HTMLCollection]: "div"}

const elementSpan = document.getElementsByTagName('span')
data[elementSpan] //  "div"

複製程式碼

element 被轉化成了'[object HTMLCollection]' 只是個字串。 並不能達到通過element 取到值的效果。但是,使用Map結構可以。 Map結構與物件很相似也是鍵值對, 但Map 的鍵可以不是字串,可以是各種型別的值(包括物件)。如果你需要“鍵值對”的資料結構,MapObject 更合適。

const dataMap = new Map()
dataMap.set(element, 'div')
dataMap.get(element)  // div

//  has,delete
dataMap.has(element)    //  true
dataMap.delete(element) //  true
dataMap.has(element)    //  false

複製程式碼

以上 new Map() 例項物件為一個Map結構,提供了setgethasdelete幾個方法。輕鬆了實現了增刪改查。 建構函式Map 可以接受一個陣列作為引數。

const map = new Map([ ['key1', 'value1'], ['key2', 'value2'] ])

複製程式碼

任何具有 Iterator 介面、且每個成員都是一個雙元素的陣列的資料結構都可以當作Map建構函式的引數。如:陣列,Set結構, Map結構。

const set = new Set([['a', 1],['b', 2]]);
const m1 = new Map(set);
m1.get('a') // 1

const m2 = new Map([['c', 3]]);
const m3 = new Map(m2);
m3.get('c') // 3
複製程式碼

注意:只有對同一個物件的引用,Map 結構才將其視為同一個鍵。

const k1 = {a: 1 }
const k2 = {a: 1 }
const map = new Map()

map.set(k1, 111)
map.get(k2) // undefined

map.set(k2, 222)
map.get(k1) //  111
map.get(k2) //  222
複製程式碼

因為即便值相同,但是兩個物件記憶體地址是不一樣的。

這就解決了同名屬性碰撞(clash)的問題,我們擴充套件別人的庫的時候,如果使用物件作為鍵名,就不用擔心自己的屬性與原作者的屬性同名。

如果 Map 的鍵是一個簡單型別的值(數字、字串、布林值),則只要兩個值嚴格相等,Map 將其視為一個鍵,比如0和-0就是一個鍵。

例項的屬性和操作方法

  1. set(key, value): set方法設定鍵名key對應的鍵值為value,然後返回整個 Map 結構。如果key已經有值,則鍵值會被更新,否則就新生成該鍵。
  2. get(key):get方法讀取key對應的鍵值,如果找不到key,返回undefined
  3. size 屬性: 返回 Map 結構的成員總數。
  4. has(key): has方法返回一個布林值,表示某個鍵是否在當前 Map 物件之中。
  5. delete(key): delete方法刪除某個鍵,返回true。如果刪除失敗,返回false
  6. clear(): clear方法清除所有成員,沒有返回值。
const map = new Map();
//  可以採用鏈式寫法。
map.set('a', 1).set('b', 2)

map.size          // 2

map.get('b')      // 2
map.set('b', 222)
map.get('b')      // 222
map.get('c')      // undefined

map.has('b')      // true

map.delete('b')   // true
map.has('b')      // false

map.clear()
map.size          // 0
複製程式碼

遍歷方法

  1. keys():返回鍵名的遍歷器
  2. values():返回鍵值的遍歷器
  3. entries():返回鍵值對的遍歷器
  4. forEach():使用回撥函式遍歷每個成員

需要特別注意的是,Map 的遍歷順序就是插入順序。

const map = new Map([['a', 1], ['b',  2]]);

for (let key of map.keys()) {
  console.log(key);
}
// "a"
// "b"

for (let value of map.values()) {
  console.log(value);
}
// 1
// 2

for (let item of map.entries()) {
  console.log(item);
}
// ["a", 1]
// ["b", 2]

// 或者
for (let [key, value] of map.entries()) {
  console.log(key, value);
}
// "a" 1
// "b" 2

// 等同於使用map.entries()
for (let [key, value] of map) {
  console.log(key, value);
}
// "a" 1
// "b" 2
複製程式碼

與其他資料結構的互相轉換

(1)Map 與陣列的互換

const arr1 = [[{'k1': 11}, 11 ],['a', 1]]
const map = new Map(arr1)

[...map]          // [[{'k1': 11}, 11 ],['a', 1]]
Array.from(map)   // [[{'k1': 11}, 11 ],['a', 1]]
複製程式碼

(2) Map 與物件的互換

如果所有 Map 的鍵都是字串,它可以轉為物件。

const obj1 = { a:1, b:2 }
const obj2 = {}
const map = new Map()

for(let key of Object.keys(obj1)) {
  map.set(key, obj1[key])
}

console.log(map)    // Map(2) {"a" => 1, "b" => 2}

for (let [key,value] of map) {
  obj2[key] = value
}

console.log(obj2)  // {a: 1, b: 2}
複製程式碼

(3) JSON 要轉換成 Map 可以先轉換成陣列或者物件,然後再轉換。

WeakMap

WeakMap結構與Map結構類似,也是用於生成鍵值對的集合。

const k1 = [1, 2, 3];
const k2 = [4, 5, 6];
const wm2 = new WeakMap([[k1, 'foo'], [k2, 'bar']]);
wm2.get(k2) // "bar"
複製程式碼

WeakMap與Map的區別有兩點。

  1. WeakMap只接受物件作為鍵名(null除外),不接受其他型別的值作為鍵名。
  2. WeakMap的鍵名所指向的物件,不計入垃圾回收機制。
const map = new WeakMap();
map.set(1, 2)
// TypeError: 1 is not an object!

複製程式碼

WeakMap 的語法:

WeakMapMap 相似但有兩個區別:

  1. 沒有遍歷操作(即沒有key()、values()和entries()方法)。
  2. 無法清空,即不支援clear方法。 因此,WeakMap只有四個方法可用:get()、set()、has()、delete()。

WeakMap 的用途

WeakMap 應用的典型場合就是 DOM 節點作為鍵名。 WeakMap 的另一個用處是部署私有屬性。

const _counter = new WeakMap();
const _action = new WeakMap();

class Countdown {
  constructor(counter, action) {
    _counter.set(this, counter);
    _action.set(this, action);
  }
  dec() {
    let counter = _counter.get(this);
    if (counter < 1) return;
    counter--;
    _counter.set(this, counter);
    if (counter === 0) {
      _action.get(this)();
    }
  }
}

const c = new Countdown(2, () => console.log('DONE'));

c.dec()
c.dec()

複製程式碼

習題: 一· 求set 的值

let arr1 = [1, 2, 3, '3', 2]
const set = new Set(arr1)
複製程式碼

二. 求遍歷的輸出

for (let [key, value] of set.entries()) {
  console.log(key === value)
}
複製程式碼

三. set 轉換數成陣列
四. 求 data[obj1],

const obj1 = { a: 1}
const obj2 = { b: 2}

const data = {}
data[obj1] = 11
data[obj2] = 22

// 求 data[obj1]

複製程式碼

五. 1.求 map.get({a: 1}), 2.如果 map 要轉換成其他結果,應該是物件還是陣列

const map = new Map()
map.set({a: 1}, 111)

map.get({a: 1})


複製程式碼

相關文章