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
介面的其他資料結構)作為引數,用來初始化。
特點:
- 以陣列為引數, 可以去重
- 向
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
例項的方法分為兩大類:操作方法(用於運算元據)和遍歷方法(用於遍歷成員)。
四個操作方法。
add(value)
:新增某個值,返回Set
結構本身。delete(value)
:刪除某個值,返回一個布林值,表示刪除是否成功。has(value)
:返回一個布林值,表示該值是否為Set
的成員。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
複製程式碼
四個遍歷方法
keys()
:返回鍵名的遍歷器values()
:返回鍵值的遍歷器entries()
:返回鍵值對的遍歷器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方法,用於對每個成員執行某種操作,沒有返回值。
不過key
和value
是同一個
set.forEach((value, key) => console.log(key + ' : ' + value))
// a: a
// b: b
// c: c
複製程式碼
遍歷的應用
- 去重
- 實現並集(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
的鍵可以不是字串,可以是各種型別的值(包括物件)。如果你需要“鍵值對”的資料結構,Map
比 Object
更合適。
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
結構,提供了set
、get
、has
、delete
幾個方法。輕鬆了實現了增刪改查。
建構函式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就是一個鍵。
例項的屬性和操作方法
- set(key, value):
set
方法設定鍵名key對應的鍵值為value
,然後返回整個Map
結構。如果key
已經有值,則鍵值會被更新,否則就新生成該鍵。 - get(key):
get
方法讀取key
對應的鍵值,如果找不到key
,返回undefined
。 - size 屬性: 返回
Map
結構的成員總數。 - has(key):
has
方法返回一個布林值,表示某個鍵是否在當前Map
物件之中。 - delete(key):
delete
方法刪除某個鍵,返回true
。如果刪除失敗,返回false
。 - 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
複製程式碼
遍歷方法
keys()
:返回鍵名的遍歷器values()
:返回鍵值的遍歷器entries()
:返回鍵值對的遍歷器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的區別有兩點。
- WeakMap只接受物件作為鍵名(null除外),不接受其他型別的值作為鍵名。
- WeakMap的鍵名所指向的物件,不計入垃圾回收機制。
const map = new WeakMap();
map.set(1, 2)
// TypeError: 1 is not an object!
複製程式碼
WeakMap 的語法:
WeakMap
與 Map
相似但有兩個區別:
- 沒有遍歷操作(即沒有key()、values()和entries()方法)。
- 無法清空,即不支援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})
複製程式碼