ES6 Set 和 Map

[ES6] Set 与 Map

Set

Set 类似数组,区别在于一个 Set 中的值都是唯一的,没有重复值。

创建一个 Set

1
const s = new Set()

Set 函数可以接受一个数组(或者具有 iterable 接口的其他数据结构)作为参数来初始化。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// 例一
const set = new Set([1, 2, 3, 4, 4]);
[...set]
// [1, 2, 3, 4]

// 例二
const items = new Set([1, 2, 3, 4, 5, 5, 5, 5]);
items.size // 5

// 例三
const set = new Set(document.querySelectorAll('div'));
set.size // 56

// 类似于
const set = new Set();
document
.querySelectorAll('div')
.forEach(div => set.add(div));
set.size // 56

用 Set 去重

由于 Set 内的值都不重复,因此可以用来去重。

  • 数组去重

    1
    [...new Set(array)]
  • 字符串去重

    1
    [...new Set('ababbc')].join('')

注意点:

  • NaN 在 Set 内部被认为是相等的。

  • 对象总是不相等的。

属性和方法

Set 结构的实例有以下属性。

Set.prototype.constructor:构造函数,默认就是 Set 函数。Set.prototype.size:返回 Set 实例的成员总数。

Set 实例的方法分为两大类:操作方法(用于操作数据)和遍历方法(用于遍历成员)。

四个操作方法:

  • Set.prototype.add(value):添加某个值,返回 Set 结构本身。

  • Set.prototype.delete(value):删除某个值,返回一个布尔值,表示删除是否成功。

  • Set.prototype.has(value):返回一个布尔值,表示该值是否为 Set 的成员。

  • Set.prototype.clear():清除所有成员,没有返回值。

四个遍历方法:

  • Set.prototype.keys():返回键名的遍历器

  • Set.prototype.values():返回键值的遍历器

  • Set.prototype.entries():返回键值对的遍历器

  • Set.prototype.forEach():使用回调函数遍历每个成员

注意:

  • Set 的遍历顺序就是插入顺序

  • keysvaluesentries 方法返回的都是遍历器对象。因此,由于 Set 结构没有键名只有键值,keysvalues 方法行为一致。

  • entries 返回的遍历器同事包括键名和键值,因此会输出一个包含两项的数组,这两项完全相等。

  • for...of 循环实际上是 values 方法

  • ...扩展运算符内部使用的是for...of方法,也就是values方法。因此可以用该方式将 Set 展开为数组,从而间接的使用数组的方法,例如mapfilter

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
let set = new Set(['red', 'green', 'blue']);

for (let item of set.keys()) {
console.log(item);
}
// red
// green
// 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"]

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

WeakSet

与 Set 的区别:

首先,WeakSet 的成员只能是对象,而不能是其他类型的值。其次,WeakSet 中的对象都是弱引用,即垃圾回收机制不考虑 WeakSet 对该对象的引用,也就是说,如果其他对象都不再引用该对象,那么垃圾回收机制会自动回收该对象所占用的内存,不考虑该对象还存在于 WeakSet 之中。

由于上面这个特点,WeakSet 的成员是不适合引用的,因为它会随时消失。另外,由于 WeakSet 内部有多少个成员,取决于垃圾回收机制有没有运行,运行前后很可能成员个数是不一样的,而垃圾回收机制何时运行是不可预测的,因此 ES6 规定 WeakSet 不可遍历。

用处:

WeakSet 的一个用处,是储存 DOM 节点,而不用担心这些节点从文档移除时,会引发内存泄漏。

这里使用 WeakSet 的好处是,foos 对实例的引用,不会被计入内存回收机制,所以删除实例的时候,不用考虑 foos,也不会出现内存泄漏。

1
2
3
4
5
6
7
8
9
10
11
const foos = new WeakSet()
class Foo {
constructor() {
foos.add(this)
}
method () {
if (!foos.has(this)) {
throw new TypeError('Foo.prototype.method 只能在Foo的实例上调用!');
}
}
}

Map

Map 类似与对象,区别在于,对象只能使用字符串作为键,而 Map 可以使用各种类型的值(包括对象)作为键。

创建一个 Map

1
const m = new Map()

Map 可以接受一个数组作为参数,该数组的成员是一个个表示键值对的数组:

1
2
3
4
5
6
7
8
9
10
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"

实际上是将参数数组遍历,调用了set方法。

1
2
3
4
5
6
7
8
9
10
const items = [
['name', '张三'],
['title', 'Author']
];

const map = new Map();

items.forEach(
([key, value]) => map.set(key, value)
);

也就是说,任何具有 Iterator 接口、且每个成员都是一个双元素的数组的数据结构都可以当作 Map 构造函数的参数。

1
2
3
4
5
6
7
8
9
10
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

注意:只有对同一个对象的引用,Map 结构才将其视为同一个键,也就是说,[1][1]虽然值相同,但是是两个不同的数组实例,Map 不会将其视为同一个键:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
const map = new Map();

map.set(['a'], 555);
map.get(['a']) // undefined

const k1 = ['a'];
const k2 = ['a'];

map
.set(k1, 111)
.set(k2, 222);

map.get(k1) // 111
map.get(k2) // 222

属性和方法

属性:

Map.prototype.size返回 Map 结构的成员总数

四个操作方法:

  • Map.prototype.set(key, value) 无则生成,有则更新,返回这个 Map 对象,可以链式操作

  • Map.prototype.get(key) 无则返回undefined

  • Map.prototype.has(key) 有返回true,无返回false

  • Map.prototype.clear() 清空,无返回值

四个遍历方法:

  • Map.prototype.keys():返回键名的遍历器。

  • Map.prototype.values():返回键值的遍历器。

  • Map.prototype.entries():返回所有成员的遍历器。

  • Map.prototype.forEach():遍历 Map 的所有成员。

注意:

Map 的遍历顺序为插入顺序
Map 的默认遍历器接口是 entries 方法。

1
map[Symbol.iterator] === map.entries

Map 可以通过...操作符快速转为数组结构,可以变相的使用数组的方法,例如filtermap

WeakMap

区别:

WeakMap 只接受对象作为键名(null 除外),不接受其他类型的值作为键名。

WeakMap的键名所指向的对象,不计入垃圾回收机制。

使用场景:

WeakMap的键所对应的对象,可能会在将来消失。WeakMap结构有助于防止内存泄漏。

例如:

一个典型应用场景是,在网页的 DOM 元素上添加数据,就可以使用WeakMap结构。当该 DOM 元素被清除,其所对应的WeakMap记录就会自动被移除。


ES6 Set 和 Map
https://bald3r.wang/2023/07/26/Set-和-Map/
作者
Allen
发布于
2023年7月26日
许可协议