明人不说暗话,五分钟带你搞懂 WeakMap
什么是 WeakMap ?
WeakMap 提供了一种从外部扩展对象而不干扰垃圾收集的方法。
它是一个 Map 字典,其中的键很弱,也就是说,如果对该键的所有引用都丢失,并且不再有对该值的引用,则可以对该值进行垃圾回收。
基本上,如果你要往对象上添加数据,又不想干扰垃圾回收机制,就可以使用 WeakMap。
需要注意的是,WeakMap 的 key 只能是 Object 类型。 原始数据类型是不能作为 key 的(比如 Symbol)。
举个栗子
假设有一个 API 给我们提供了一个特定的对象:
var obj = getObjectFromLibrary();
现在,我有一个使用该对象的方法:
function useObj(obj){
doSomethingWith(obj);
}
我想跟踪该对象被调用的次数,如果调用次数超过 N,就进行上报处理。
大部分人很容易想到使用Map来进行实现:
var map = new Map();
function useObj(obj){
doSomethingWith(obj);
var called = map.get(obj) || 0;
called++;
if(called > 10) report();
map.set(obj, called);
}
这个方法是可行的,但它存在内存泄漏。
为什么使用 Map 会造成内存泄漏呢?下面我们一起来看一下。
为什么会造成内存泄漏?
在上面的例子中我们使用的是 Map,并使传入的对象作为映射键。
问题在于,这个对象永远不会从Map上被移除,因为我们不知道什么时候该去做这件事情。
所以总是有一个对它的引用,它永远不会被垃圾回收。
但是 在WeakMap 中,只要对象的所有其他引用都消失了,就可以从 WeakMap 中清除该对象。
WeakMap 示例
现在我们来看一下如何使用 WeakMap 解决这个问题:
var map = new WeakMap();
function useObj(obj){
doSomethingWith(obj);
var called = map.get(obj) || 0;
called++;
if(called > 10) report();
map.set(obj, called);
}
现在就不存在内存泄漏了。
一个真实的场景
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节点)的数据。
- 从外部向对象添加功能。
转载自:https://juejin.cn/post/6854573210013270024