学习链接

现代 JavaScript 教程

阮一峰:ES6 教程

Map、Set、WeakMap、WeakSet、WeakRef

  • 对象,存储带有的数据的集合。
  • 数组,存储有序集合。
  • Map,类似对象,但是允许任何类型的键
  • Set,类似数组,但是成员值唯一

Map 对比 Object

Map 是一个带键的数据项的集合。

不同于对象只允许使用字符串作为键,Map 允许任何类型的键(key)。

Set 对比 Array

Set 是一个特殊的类型集合 —— “值的集合”(没有键),它的每一个值只能出现一次

对比数组方法去重,在每次插入值的时候检查数组元素是否重复,但是这样性能很差,因为目前的数组方法会遍历整个数组检查重复元素

Set 内部对唯一性检查进行了优化

Map/Set 键的迭代顺序

MapSet 中迭代总是按照值插入的顺序进行的。

所以这些集合不是无序的,但是无法对元素进行重新排序,也不能直接按其编号来获取元素。

Map/Set 键的比较

Map/Set 使用 SameValueZero 算法来比较键是否相等。

它和严格等于 === 的区别是 NaN 被看成是等于 NaN。所以 NaN 也可以被用作键。

WeakMap/WeakSet 对比 Map/Set

使用目的:防止内存泄漏

垃圾回收机制不考虑 WeakMap键名WeakSet 对于对象的引用

也就是说,如果其他对象都不再引用该对象,那么垃圾回收机制会自动回收该对象所占用的内存,不考虑该对象还存在于 WeakMap键名WeakSet 之中。

这是因为垃圾回收机制根据对象的可达性(reachability)来判断回收,如果对象还能被访问到,垃圾回收机制就不会释放这块内存。

结束使用该值之后,有时会忘记取消引用,导致内存无法释放,进而可能会引发内存泄漏。

WeakMap键名WeakSet 里面的引用,都不计入垃圾回收机制,所以就不存在这个问题。

  1. // Map
  2. let john = { name: "John" };
  3. let map = new Map();
  4. map.set(john, "...");
  5. john = null; // 覆盖引用
  6. // john 被存储在了 map 中,
  7. // 此时仍然可以使用 map.keys() 来获取它
  8. // WeakMap
  9. let john = { name: "John" };
  10. let weakMap = new WeakMap();
  11. weakMap.set(john, "..."); // 此处引用不被计入垃圾回收机制
  12. john = null; // 覆盖引用
  13. // john 后续将会被垃圾回收机制回收

只收非 null 对象

  • WeakMap 只接受对象作为键名(不接受 null

  • WeakSet 只接受对象作为成员(不接受 null

不可遍历

  • 不可遍历,无 clear()

  • size 属性,内部的成员个数取决于垃圾回收机制是否运行

WeakRef

WeakSetWeakMap 是基于弱引用的数据结构,ES2021 更进一步,提供了 WeakRef 对象,用于直接创建对象的弱引用

  1. let target = {};
  2. let wr = new WeakRef(target);

wr 就是一个 WeakRef 的实例,属于对 target 的弱引用,垃圾回收机制不会计入这个引用。

也就是说,wr 的引用不会妨碍原始对象 target 被垃圾回收机制清除。

WeakRef.prototype.deref()

  • 如果原始对象存在,该方法返回原始对象;

  • 如果原始对象已经被垃圾回收机制清除,该方法返回 undefined

注意,标准规定,一旦使用 WeakRef() 创建了原始对象的弱引用,那么在本轮事件循环(event loop),原始对象肯定不会被清除,只会在后面的事件循环才会被清除。

Map 映射

Map —— 是一个带键的数据项的集合。

操作方法和属性

  • new Map([iterable]) —— 创建 map,可选择带有 [key,value] 对的 iterable(例如数组)来进行初始化。
  • map.set(key, value) —— 根据键存储值,返回 map 自身。
  • map.get(key) —— 根据键来返回值,如果 map 中不存在对应的 key,则返回 undefined
  • map.has(key) —— 如果 key 存在则返回 true,否则返回 false
  • map.delete(key) —— 删除指定键对应的值,如果在调用时 key 存在,则返回 true,否则返回 false
  • map.clear() —— 清空 map 。
  • map.size —— 返回当前元素个数。

遍历方法

  • map.keys() —— 遍历并返回一个包含所有键的可迭代对象
  • map.values() —— 遍历并返回一个包含所有值的可迭代对象
  • map.entries() —— 遍历并返回一个包含所有实体 [key, value]可迭代对象for..of 在默认情况下使用的就是这个
  • map.forEach((value, key, map) => {})

Object.entries:从对象创建 Map

当创建一个 Map 后,我们可以传入一个带有键值对的数组(或其它可迭代对象)来进行初始化。

Object.entries(obj) 方法返回对象的键/值对数组,该数组格式完全按照 Map 所需的格式。

  1. // 键值对 [key, value] 数组
  2. const map = new Map([
  3. ['1', 'str1'],
  4. [true, 'bool1']
  5. ]);
  6. // 对象的键值对数组
  7. let obj = {
  8. name: "John",
  9. age: 30
  10. };
  11. const map = new Map(Object.entries(obj));

Object.fromEntries:从 Map 创建对象

Object.fromEntries 方法根据一个具有 [key, value] 键值对的数组创建一个对象。

  1. const prices = Object.fromEntries([
  2. ['banana', 1],
  3. ['orange', 2],
  4. ['meat', 4]
  5. ]);
  6. // prices 为 { banana: 1, orange: 2, meat: 4 }
  7. const map = new Map();
  8. map.set('banana', 1);
  9. map.set('orange', 2);
  10. const obj = Object.fromEntries(map.entries()); // 创建一个普通对象(plain object)
  11. // 或者
  12. const obj = Object.fromEntries(map);
  13. // obj 为 { banana: 1, orange: 2}

Set 集合

Set —— 是一组唯一值的集合。

操作方法和属性

  • new Set([iterable]) —— 创建 set,可选择带有 iterable(例如数组)来进行初始化。
  • set.add(value) —— 添加一个值(如果 value 存在则不做任何修改),返回 set 本身。
  • set.delete(value) —— 删除值,如果 value 在这个方法调用的时候存在则返回 true ,否则返回 false
  • set.has(value) —— 如果 value 在 set 中,返回 true,否则返回 false
  • set.clear() —— 清空 set。
  • set.size —— 元素的个数。

遍历方法

  • set.keys() —— 遍历并返回一个包含所有值的可迭代对象
  • set.values() —— 与 set.keys() 作用相同,这是为了兼容 Mapfor..of 在默认情况下使用的就是这个
  • set.entries() —— 遍历并返回一个包含所有的实体 [value, value]可迭代对象,它的存在也是为了兼容 Map
  • set.forEach((value, valueAgain, set) => {}),同样为了兼容 Map

WeakMap 弱映射

只支持 getsethasdelete 方法。

WeakSet 弱集合

支持 addhasdelete 方法,

WeakRef 弱引用

WeakSet 和 WeakMap 是基于弱引用的数据结构,ES2021 更进一步,提供了 WeakRef 对象,用于直接创建对象的弱引用。

  1. let target = {};
  2. let wr = new WeakRef(target);

上面示例中,target 是原始对象,构造函数 WeakRef() 创建了一个基于 target 的新对象 wr。这里,wr 就是一个 WeakRef 的实例,属于对 target 的弱引用,垃圾回收机制不会计入这个引用,也就是说,wr 的引用不会妨碍原始对象 target 被垃圾回收机制清除。

WeakRef 实例对象有一个 deref() 方法,如果原始对象存在,该方法返回原始对象;如果原始对象已经被垃圾回收机制清除,该方法返回 undefined

  1. let target = {};
  2. let wr = new WeakRef(target);
  3. let obj = wr.deref();
  4. if (obj) { // target 未被垃圾回收机制清除
  5. // ...
  6. }

上面示例中,deref()方法可以判断原始对象是否已被清除。

弱引用对象的一大用处,就是作为缓存,未被清除时可以从缓存取值,一旦清除缓存就自动失效。

注意,标准规定,一旦使用WeakRef()创建了原始对象的弱引用,那么在本轮事件循环(event loop),原始对象肯定不会被清除,只会在后面的事件循环才会被清除。