初学WeakMap概念和应用场景

292 阅读4分钟

什么是 WeakMap ?

WeakMap 提供了一种从外部扩展对象而不干扰垃圾收集的方法。

它是一个 Map 字典,其中的键很弱,也就是说,如果对该键的所有引用都丢失,并且不再有对该值的引用,则可以对该值进行垃圾回收。

基本上,如果你要往对象上添加数据,又不想干扰垃圾回收机制,就可以使用 WeakMap。

需要注意的是,WeakMap 的 key 只能是 Object 类型。 原始数据类型是不能作为 key 的(比如 Symbol)。

WeakMap VS Map

假设一个以下场景:
要求:如果A对象 作为B对象的键、值或子元素时,将A对象引用设置为 null 时,该对象是不会被回收的,依然存在
例如

var a = {}; 
var map = new Map();
map.set(a, '学前端')

a = null; 
console.log(map.keys()) // MapIterator {{}}
console.log(map.values()) // MapIterator {"学前端"}

如果想让 a 置为 null 时,该对象被回收,该怎么做呢?

ES6 考虑到了这一点,推出了: WeakMap 。它对于值的引用都是不计入垃圾回收机制的,所以名字里面才会有一个"Weak",表示这是弱引用(对对象的弱引用是指当该对象应该被GC回收时,不会阻止GC的回收行为)(就是说不会管你引用多少次,我照样收)

var a = {};
var map = new WeakMap();
map.set(a, '学前端')
map.get(a)

a = null;
console.log(map.get('a')) // undefind
//`WeakMap` 不能被遍历,用不了 .keys  .values 方法

真实的场景的一个例子

WeakMap 可以用来从外部扩展对象,让我们再一起来看一个现实世界中和 Node.js 相关的例子。

假设你想要跟踪 Node.js 中所有当前被 rejected 的 Promises。

但是,你不想他们被垃圾回收,因为如果他们被垃圾回收了,这些 Promises 的引用就会丢失。

然而,如果保留对 Promises 的引用,则会导致内存泄漏,因为不会发生垃圾回收。

现在看来,无论如何你都需要保留每个被 rejected 的引用,但是我们可以使用 WeakMap ,既可以拿到每个 Promise 的引用,又可以使其被垃圾回收。

WeakMap 是怎么做到的?

WeakMap 对象是一组键/值对的集合,其中的键是弱引用的。

这意味着,我们无法对其进行枚举并且获得其 values。

但是 WeakMap 中,我们可以基于键存储数据,当该键被垃圾回收时,值也会被垃圾回收。

这意味着,你可以保持 Promise 的状态,并且该对象仍然可以被垃圾回收。

以后,如果你得到一个对象的引用,你可以检查你是否有任何与之相关的状态并报告它。

下面是Petka Antonov用来实现 未经处理的Promise钩子如下所示:

process.on('unhandledRejection', function(reason, p) {
    console.log("Unhandled Rejection at: Promise ", p, " reason: ", reason);
    // 进行其他的处理
});

我们在 WeakMap 中保存了相关 Promise 的信息,并且可以知道何时处理了被 rejected 的 promise。

其他使用场景

一些适合使用 WeakMap 防止内存泄漏的场景包括:

  • 保留关于特定对象的私有数据,并且只将对该对象的访问权限授予Map的引用者。
  • 保存有关对象的数据而不更改它们或产生开销。
  • 在浏览器中保存有关宿主对象(如DOM节点)的数据。
  • 从外部向对象添加功能。

总结

  • Map 的键可以是任意类型,WeakMap 只接受对象作为键(null除外),不接受其他类型的值作为键
  • Map 的键实际上是跟内存地址绑定的,只要内存地址不一样,就视为两个键; WeakMap 的键是弱引用,键所指向的对象可以被垃圾回收,此时键是无效的
  • Map 可以被遍历, WeakMap 不能被遍历(WeakMap 中,每个键对自己所引用对象的引用都是弱引用,在没有其他引用和该键引用同一对象,这个对象将会被垃圾回收(相应的key则变成无效的),所以,WeakMap 的 key 是不可枚举的。)

方法:

  • has(key):判断是否有 key 关联对象
  • get(key):返回key关联对象(没有则则返回 undefined)
  • set(key):设置一组key关联对象
  • delete(key):移除 key 的关联对

补充

1、注意,WeakMap 弱引用的只是键名,而不是键值。键值依然是正常引用。 2、除了 WeakMap 还有 WeakSet 都是弱引用,可以被垃圾回收机制回收,可以用来保存DOM节点,不容易造成内存泄漏