ES6中的Map、Set、WeakMap和WeakSet

178 阅读10分钟

Set 是叫集合的数据结构,Map 是叫字典的数据结构。

  • 相同点:SetMap 存储不重复的值。

  • 不同点:Set[value, value] 存储数据,Map[key, value] 存储数据。

1、总结

Set

  • 成员唯一、无序且不重复。
  • [value, value],键值与键名是一致的(或者说只有键值,没有键名)。
  • 遍历顺序按插入顺序。
  • 可以遍历,方法有:adddeletehassizeclear

Map

  • 本质上是键值对的集合,其中的键和值可以是任意类型的,类似集合。不同之处在于,Map 可以使用任意类型作为键,而对象只能使用字符串或 Symbol 类型作为键。
  • 遍历顺序按插入顺序。
  • 可以遍历,方法很多可以跟各种数据格式转换,方法有:getsethasdeletesizeclear

WeakSet

  • 成员都是对象,其中每个值都是唯一的,并且没有特定的顺序。
  • 成员都是弱引用,可以被垃圾回收机制回收,可以用来保存 DOM 节点,不容易造成内存泄漏。
  • 不能遍历,方法有adddeletehas
  • 没有 size 属性,因为其内容可能会随时被垃圾回收。

WeakMap

  • 只接受对象作为键名(null除外),不接受其他类型的值作为键名,并且没有特定的顺序。它们的键必须是对象,而值可以是任何类型。
  • 键名是弱引用,键值可以是任意的,键名所指向的对象可以被垃圾回收,此时键名是无效的。
  • 不能遍历,方法有getsethasdelete
  • 没有 size 属性,因为其内容可能会随时被垃圾回收。

2、Set

Set 本身是一个构造函数,用来生成 Set 数据结构。Set 函数可以接受一个数组(或者具有 iterable 接口的其他数据结构)作为参数,用来初始化。Set 对象允许你存储任何类型的值,无论是原始值或者是对象引用。它类似于数组,但是成员的值都是唯一的,没有重复的值。

2.1、介绍

  • Set 类似数组,成员唯一。
  • Set 本身是构造函数,生成 Set 数据结构。
  • Set 可以存储任意类型的唯一值。
  • Setkey 的值和 value 的值一样
let s = new Set();

[1, 2, 3, 4, 5, 6, 1, 2].map(item=>{
  s.add(item); 
}); // [1, 2, 3, 4, 5, 6]

// 数组去重
let arr = new Set([1, 2, 3, 4, 3, 2, 4, 5]);
console.log([...new Set(arr)]);

// or
let arr = new Set([1, 2, 3, 4, 3, 2, 4, 5]);
console.log(Array.from(new Set(arr)));

注:Set 里面加入值时不会发送类型转换,及 5'5' 是两个不同的值,Set 判断两个值是否相等类似于 ‘===’。 但判断 NaN 时,'===' 的判断 NaN 不等于 NaNSet 的判断 NaN 等于 NaN

2.2、操作

  • add(value):新增,相当于 Arraypush
let arr = new Set([1, 2, 3, 4, 3, 2, 4, 5]);
arr.add(9); // 1,2,3,4,9
  • delete(value):存在就删除里面的 value
let arr = new Set([1, 2, 3, 4, 3, 2, 4, 5]);
arr.delete(1); // true, 删除成功
  • has(value):判断 Set 里面有没有这个值。
let arr = new Set([1, 2, 3, 4, 3, 2, 4, 5]);
arr.has(1); // true 
  • clear():清空集合。
let arr = new Set([1, 2, 3, 4, 3, 2, 4, 5]);
arr.clear(); 
  • size:返回实例成员总数。
let arr = new Set([1, 2, 3, 4, 3, 2, 4, 5]);
arr.size; // 5

2.3、遍历

  • keys():返回一个包含集合中所有键的迭代器。
let arr = new Set([1, 2, 3, 4, (a = { a: 1, b: 2 })]);
arr.keys(); // SetIterator {1, 2, 3, 4, {…}}
  • values():返回一个包含集合中所有值得迭代器。
let arr = new Set([1, 2, 3, 4, (a = { a: 1, b: 2 })]);
arr.keys(); // SetIterator {1, 2, 3, 4, {…}}
  • entries():返回一个包含 Set 对象中所有元素得键值对迭代器。
let arr = new Set([1, 2, 3, 4, (a = { a: 1, b: 2 })]);
arr.entries(); // SetIterator {1 => 1, 2 => 2, 3 => 3, 4 => 4, {…} => {…}}
  • forEach():使用回调函数遍历每个成员。
let arr = new Set([1, 2, 3, 4, (a = { a: 1, b: 2 })]);
arr.forEach((item) => console.log(item)); // 1 2 3 4 { a: 1, b: 2 }

2.4、Array 和 Set 对比

  • ArrayindexOf 方法比 Sethas 方法效率低下。
  • Set 不含有重复值(可以利用这个特性实现对一个数组的去重)。
  • Set 通过 delete 方法删除某个值,而 Array 通过 splice。两者的使用方便程度前者更优。
  • Array 的很多新方法 mapfiltersomeevery 等是 Set 没有的(但是通过两者可以互相转换来使用)。

2.5、Set 的应用

  • Array.from 方法可以将 Set 结构转为数组。
const items = new Set([1, 2, 3, 4, 5]);
const array = Array.from(items);
  • 数组去重
// 去除数组的重复成员
[...new Set(array)];

Array.from(new Set(array));
  • 数组的 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}
  • 实现并集 (Union)、交集 (Intersect) 和差集
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}

3、Map

Map 中存储的是 key-value 形式的键值对, 其中的 key 和 value 可以是任何类型的, 即对象也可以作为 key。 Map 的出现,就是让各种类型的值都可以当作键。Map 提供的是 “值-值” 的对应,是一种更完善的 Hash 结构实现。

3.1、介绍

  • Map是键值对的结构,查找速度极快。
  • 类似于对象的数据结构,成员键是任何类型的值。
let a = new Map();
let obj = { a: 1 };
a.set('a', obj); // Map(1) {'a' => {…}}
a.get('a'); // {a: 1}
a.has('a'); // true
a.delete('a'); // true (删除成功)
a.has('a'); // false

3.2、实例属性和方法

  • size 属性:返回 Map 结构的成员总数。
  • set(key, value)set 方法设置 key 所对应的键值,然后返回整个 Map 结构。如果 key 已经有值,则键值会被更新,否则就新生成该键,set 方法返回的是 Map 本身,因此可以采用链式写法。
  • get(key)get 方法读取 key 对应的键值,如果找不到 key,返回 undefined
  • has(key)has 方法返回一个布尔值,表示某个键是否在 Map 数据结构中。
  • delete(key)delete 方法删除某个键,返回 true。如果删除失败,返回 false
  • clear()clear 方法清除所有成员,没有返回值。
  • keys():返回以键为遍历器的对象。
  • values():返回以值为遍历器的对象。
  • entries():返回以键和值为遍历器的对象。
  • forEach():使用回调函数遍历每个成员。
let map = new Map([
  [1, 'one'],
  [2, 'two'],
  [3, 'three'],
]);

map.keys(); // MapIterator {1, 2, 3}
[...map.keys()]; // [1, 2, 3]

map.values(); // MapIterator {'one', 'two', 'three'}
[...map.values()]; // ['one', 'two', 'three']

map.entries(); // MapIterator {1 => 'one', 2 => 'two', 3 => 'three'}
[...map.entries()]; // [[1, 'one'], [2, 'two'], [3, 'three']]

map; // Map(3) {1 => 'one', 2 => 'two', 3 => 'three'}
[...map]; // [[1, 'one'], [2, 'two'], [3, 'three']]

Object.fromEntries()

Object.fromEntries() 方法是 Object.entries() 的逆操作,用于将一个键值对数组转为对象。

let map = new Map([
  [1, 'one'],
  [2, 'two'],
  [3, 'three'],
]);

map; // Map(3) {1 => 'one', 2 => 'two', 3 => 'three'}

Object.fromEntries(map); // {1: 'one', 2: 'two', 3: 'three'}

应用场景:

  • 将键值对的数据结构还原为对象,因此特别适合将 Map 结构转为对象。
  • 配合 URLSearchParams 对象,将查询字符串转为对象。
Object.fromEntries(new URLSearchParams('name=jack&age=24')); // {name: 'jack', age: '24'}

3.3、Map 和 Object 的区别

  • Object 对象有原型,也就是说他有默认的 key 值在对象上面,除非我们使用 Object.create(null) 创建一个没有原型的对象。
  • Object 对象中,只能把 StringSymbol 作为 key 值,但是在 Map 中,key 值可以是任何基本类型(StringNumberBooleanundefinedNaN…),或者对象(MapSetObjectFunctionSymbol…),但是 null 不能作为 key 值。
  • 通过 Map 中的 size 属性,可以很方便地获取到 Map 长度,要获取 Object 的长度,只能手动计算。

3.4、总结

  • 遍历顺序:插入顺序
  • 对同一个键多次赋值,后面的值将覆盖前面的值。
  • 对同一个对象的引用,被视为一个键。
  • 对同样值的两个实例,被视为两个键。
  • 键跟内存地址绑定,只要内存地址不一样就视为两个键。
  • 添加多个以 NaN 作为键时,只会存在一个以 NaN 作为键的值。
  • Object 结构提供字符串—值的对应,Map 结构提供值—值的对应。

4、WeakSet

WeakSet 结构与 Set 类似,也是不重复的值的集合,但成员值只能是对象。

4.1、介绍

  • 成员都是数组和类似数组的对象,若调用 add() 方法时传入了非数组和类似数组的对象的参数,就会抛出错误。
  • 成员都是弱引用,可以被垃圾回收机制回收,可以用来保存 DOM 节点,不容易造成内存泄漏。
  • WeakSet 不可迭代,因此不能被用在 for-of 等循环中。
  • 入参具有 Iterator 接口的数据结构。
  • WeakSet 没有 size 属性。
const a = [1, 2, [1, 2]];
new WeakSet(a); // Uncaught TypeError: Invalid value used in weak set

weakSetSet 的区别:

  1. WeakSet 只能是对象的集合,不像 Set 一样什么类型数据都能存储。
  2. WeakSet 集合里面对对象的引用是弱引用,即如果集合里面的对象没有被外部引用,就会被回收掉。

4.2、方法

  • add(value):添加值,返回实例。
  • delete(value):删除值,返回布尔。
  • has(value):检查值,返回布尔。
const weakSet = new WeakSet();
const obj = {};

weakSet.add(obj); // WeakSet {{…}}
weakSet.has(obj); // true
weakSet.delete(obj); // true

4.3、应用场景

  • 储存 DOM 节点:DOM 节点被移除时自动释放此成员,不用担心这些节点从文档移除时会引发内存泄漏。
  • 临时存放一组对象或存放跟对象绑定的信息:只要这些对象在外部消失,它在 WeakSet 结构中的引用就会自动消。

4.4、总结

  • 成员都是弱引用,垃圾回收机制不考虑 WeakSet 结构对此成员的引用。
  • 成员不适合引用,它会随时消失,因此 ES6 规定 WeakSet 结构不可遍历。
  • 其他对象不再引用成员时,垃圾回收机制会自动回收此成员所占用的内存,不考虑此成员是否还存在于 WeakSet 结构中。

5、WeakMap

WeakMap 结构与 Map 结构类似,也是用于生成键值对的集合,但是键必须是弱引用对象,而值可以是任意类型。

5.1、介绍

  • 只接受对象作为键名(null 除外),不接受其他类型的值作为键名。
  • 键名是弱引用,键值可以是任意的,键名所指向的对象可以被垃圾回收,此时键名是无效的。
  • 入参具有 Iterator 接口且每个成员都是一个双元素数组的数据结构。
  • 不能遍历,方法有 getsethasdelete

WeakMapMap 的区别:

  1. WeakMap 也是键值对集合,但是键必须是弱引用对象,而值可以是任意类型。
  2. WeakMap 集合里面键的对象的引用是弱引用,即如果集合里面键的对象没有被外部引用,就会被回收掉。
const weakMap = new WeakMap();

let obj = {};
let obj2 = {};
let value = {n: 1};

weakMap.set(obj, 1); // WeakMap {{…} => 1}
weakMap.set(obj2, value); // WeakMap {{…} => {…}, {…} => 1}

weakMap.has(obj); // true
weakMap.get(obj); // 1

weakMap.has(obj2); // true
weakMap.get(obj2); // {n: 1}

obj = null;
weakMap.has(obj); // false
weakMap.get(obj); // undefined

value = null;
weakMap.has(obj2); // true
weakMap.get(obj2); // {n: 1}

weakMap.delete(obj2); // true
weakMap.has(obj2); // false
weakMap.get(obj2); // undefined

5.2、方法

  • set(key,value):添加键值对,返回实例。
  • get(key): 返回键值对的值。
  • has(key): 检查键值对,返回布尔。
  • delete(key): 删除键值对,返回布尔。

5.3、应用场景

  • 储存 DOM 节点:DOM 节点被移除时自动释放此成员键,不用担心这些节点从文档移除时会引发内存泄漏。
  • 部署私有属性:内部属性是实例的弱引用,删除实例时它们也随之消失,不会造成内存泄漏。

5.4、总结

  • 成员键都是弱引用,垃圾回收机制不考虑 WeakMap 结构对此成员键的引用。
  • 成员键不适合引用,它会随时消失,因此 ES6 规定 WeakMap 结构不可遍历。
  • 其他对象不再引用成员键时,垃圾回收机制会自动回收此成员所占用的内存,不考虑此成员是否还存在于 WeakMap 结构中。
  • 一旦不再需要,成员会自动消失,不用手动删除引用。
  • 弱引用的只是键而不是值,值依然是正常引用。
  • 即使在外部消除了成员键的引用,内部的成员值依然存在。