Set和Map数据结构
说明
Set和WeakSet Map和WeakMap笔记整理
# Set
是ES6新的数据结构
它类似于数组,但是成员的值都是唯一的,没有重复的值,
// 数组去重
const s = new Set()
const a = [1,1,2,3,4,4,5]
a.forEach(x => {
s.add(x);
});
for(i of s){
console.log(i); // 1 2 3 4 5
}
-----------------------
function qu(array) {
// return [...new Set(array)]
return Array.from(new Set(array))
}
console.log(qu([1,1,2,3,4]));
// Set函数可以接受一个数组(或者具有 iterable 接口的其他数据结构)作为参数,用来初始化。
const set = new Set([1, 2, 3, 4, 4]);
[...set] // [1, 2, 3, 4]
[...new Set('ababbc')].join('') // "abc"
[...new Set(array)]
const items = new Set([1, 2, 3, 4, 5, 5, 5, 5]);
items.size // 5
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
向 Set 加入值的时候,不会发生类型转换,所以5
和"5"
是两个不同的值。Set 内部判断两个值是否不同,类似于精确相等运算符(===
),主要的区别是向 Set 加入值时认为NaN
等于自身,而精确相等运算符认为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
2
3
4
5
6
7
8
9
10
11
12
# Set 实例的属性和方法
const s = new Set()
s.add(1).add(2).add(2).add(5)
console.log(s.size) // 3
s.has(2) // true
s.has(3) // false
s.delete(2)
s.has(2) // false
for (const x of s) {
console.log(x) // 1 5
}
s.clear()
s.add(4)
for (const x of s) {
console.log(x) // 4
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# Set应用
let a = new Set([1, 2, 3]);
let b = new Set([4, 3, 2]);
// 并集
let c= new Set([...a,...b]);
for (const x of c) {
console.log(x) // 1,2,3,4
}
// 交集
let d = new Set(Array.from(a).filter(x=>b.has(x)))
for (const x of d) {
console.log(x) // 2,3
}
2
3
4
5
6
7
8
9
10
11
12
# WeakSet
WeakSet 的成员只能是对象,而不能是其他类型的值
WeakSet 中的对象都是弱引用,即垃圾回收机制不考虑 WeakSet 对该对象的引用,也就是说,如果其他对象都不再引用该对象,那么垃圾回收机制会自动回收该对象所占用的内存,不考虑该对象还存在于 WeakSet 之中
由于上面这个特点,WeakSet 的成员是不适合引用的,因为它会随时消失。另外,由于 WeakSet 内部有多少个成员,取决于垃圾回收机制有没有运行,运行前后很可能成员个数是不一样的,而垃圾回收机制何时运行是不可预测的,因此 ES6 规定 WeakSet 不可遍历。
const b = [3, 4];
const ws = new WeakSet(b);
// Uncaught TypeError: Invalid value used in weak set(…)
// 数组b的成员不是对象,加入 WeakSet 就会报错
-----------------------------------------
const a = [[1, 2], [3, 4]];
const ws = new WeakSet(a);
// WeakSet {[1, 2], [3, 4]}
// a数组的成员成为 WeakSet 的成员,而不是a数组本身
------------------------------------------
const a = [{b:1}];
const ws = new WeakSet(a);
ws.size // undefined
ws.forEach(x => {
console.log(x);
});
// Uncaught TypeError: ws.forEach is not a function
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# Map
JavaScript 的对象(Object),本质上是键值对的集合(Hash 结构)
传统上只能用字符串当作键。这给它的使用带来了很大的限制。为了解决这个问题,ES6 提供了 Map 数据结构。它类似于对象,也是键值对的集合,但是“键”的范围不限于字符串,各种类型的值(包括对象)都可以当作键
const data = {};
const element = document.getElementById('my');
data['a1'] = '1'
data[element] = 'metadata';
console.log(data);
// {a1: '1', [object HTMLDivElement]: 'metadata'}
// 由于对象只接受字符串作为键名,所以element被自动转为字符串[object HTMLDivElement]
------------------------------------------
const m = new Map();
const o = "";
m.set(o, 'content')
console.log(m.get(o)); // "content"
console.log(m.has(o)); // true
console.log(m.delete(o)); // true
console.log(m.has(o)); // false
2
3
4
5
6
7
8
9
10
11
12
13
14
15
作为构造函数,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"
----------------------------
// 原理
const items = [
['name', '张三'],
['title', 'Author']
];
const map = new Map();
items.forEach(
([key, value]) => map.set(key, value)
);
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
只有对同一个对象的引用,Map 结构才将其视为同一个键
let mymap = new Map()
const a = { a: 4 }
mymap.set(a, { b: 0 })
console.log(mymap.get(a)) // {b: 0}
-----------------------
let mymap = new Map()
mymap.set({ a: 4 }, { b: 0 })
console.log(mymap.get({ a: 4 })) // undefined
2
3
4
5
6
7
8
上面代码的set
和get
方法,表面是针对同一个键,但实际上这是两个不同的数组实例,内存地址是不一样的,因此get
方法无法读取该键,返回undefined
。
如果 Map 的键是一个简单类型的值(数字、字符串、布尔值),则只要两个值严格相等,Map 将其视为一个键,比如0
和-0
就是一个键,布尔值true
和字符串true
则是两个不同的键。另外,undefined
和null
也是两个不同的键。虽然NaN
不严格相等于自身,但 Map 将其视为同一个键。
let map = new Map();
map.set(-0, 123);
map.get(+0) // 123
map.set(true, 1);
map.set('true', 2);
map.get(true) // 1
map.set(undefined, 3);
map.set(null, 4);
map.get(undefined) // 3
map.set(NaN, 123);
map.get(NaN) // 123
2
3
4
5
6
7
8
9
10
11
# Map遍历方法
const map = new Map([
['F', 'no'],
['T', 'yes'],
]);
for (let key of map.keys()) {
console.log(key);
}
// "F"
// "T"
for (let value of map.values()) {
console.log(value);
}
// "no"
// "yes"
for (let item of map.entries()) {
console.log(item[0], item[1]);
}
// "F" "no"
// "T" "yes"
for (let [key, value] of map.entries()) {
console.log(key, value);
}
// "F" "no"
// "T" "yes"
// 等同于使用map.entries()
for (let [key, value] of map) {
console.log(key, value);
}
// "F" "no"
// "T" "yes"
--------------------------------------
const map0 = new Map()
.set(1, 'a')
.set(2, 'b')
.set(3, 'c');
const map1 = new Map(
[...map0].filter(([k, v]) => k < 3)
);
// 产生 Map 结构 {1 => 'a', 2 => 'b'}
const map2 = new Map(
[...map0].map(([k, v]) => [k * 2, '_' + v])
);
// 产生 Map 结构 {2 => '_a', 4 => '_b', 6 => '_c'}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
# Map转换
和数组
// map转数组
const myMap = new Map()
.set(true, 7)
.set({foo: 3}, ['abc']);
[...myMap] // [ [true, 7], [ {foo: 3}, ['abc'] ] ]
-------------------------------------
// 数组转map
new Map([
[true, 7],
[{foo: 3}, ['abc']]
])
// Map {
// true => 7,
// Object {foo: 3} => ['abc']
// }
2
3
4
5
6
7
8
9
10
11
12
13
14
15
和对象
// map转对象
function strMapToObj(strMap) {
let obj = Object.create(null);
for (let [k,v] of strMap) {
obj[k] = v;
}
return obj;
}
const myMap = new Map()
.set('yes', true)
.set('no', false);
strMapToObj(myMap) // { yes: true, no: false }
------------------------------------------------
// 对象转map 1
let obj = {"a":1, "b":2};
let map = new Map(Object.entries(obj));
----------------------------------------
// 对象转map 2
function objToStrMap(obj) {
let strMap = new Map();
for (let k of Object.keys(obj)) {
strMap.set(k, obj[k]);
}
return strMap;
}
objToStrMap({yes: true, no: false}) // Map {"yes" => true, "no" => false}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
# WeakMap
WeakMap
只接受对象作为键名(null
除外),不接受其他类型的值作为键名。
WeakMap
的键名所指向的对象,不计入垃圾回收机制。
let mapObj1 = {
name1: 'rio1'
}
let mapObj2 = {
name2: 'rio2'
}
const map1 = new Map();
const map2 = new WeakMap();
map1.set(mapObj1, 'value1');
map2.set(mapObj2, 'value2');
mapObj1 = null
mapObj2 = null;
// 控制台打印 map1 { {name1: 'rio1'} => 'value1' }
// 控制台打印 map2 WeakMap {}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
一旦不再需要时即当weakmap的key不存在任何引用时,WeakMap 里面的键名对象和所对应的键值对会自动消失,不用手动删除引用。
因为没有办法列出所有键名,某个键名是否存在完全不可预测,所以没有遍历操作
const e1 = document.getElementById('foo');
const e2 = document.getElementById('bar');
const arr = [
[e1, 'foo 元素'],
[e2, 'bar 元素'],
];
// 不需要 e1 和 e2 的时候 必须手动删除引用
arr [0] = null;
arr [1] = null;
------------------------------
const wm = new WeakMap();
const element = document.getElementById('example');
wm.set(element, 'some information');
wm.get(element) // "some information"
// WeakMap 里面对element的引用就是弱引用,不会被计入垃圾回收机制
2
3
4
5
6
7
8
9
10
11
12
13
14
15
WeakMap 弱引用的只是键名,而不是键值。键值依然是正常引用
# WeakMap在开发中应用
let myWeakmap = new WeakMap();
myWeakmap.set(
document.getElementById('logo'),
{timesClicked: 0})
;
document.getElementById('logo').addEventListener('click', function() {
let logoData = myWeakmap.get(document.getElementById('logo'));
logoData.timesClicked++;
}, false);
2
3
4
5
6
7
8
9
每当发生click
事件,就更新一下状态。我们将这个状态作为键值放在 WeakMap 里,对应的键名就是这个节点对象。一旦这个 DOM 节点删除,该状态就会自动消失,不存在内存泄漏风险
# 参考
JavaScript高级程序设计(第4版)