JavaScript 路线图—WeakMap 弱引用与 Map 强引用的区别以及 WeakMap 的妙用

1,054 阅读3分钟

小知识,大挑战!本文正在参与“程序员必备小知识”创作活动。

引用中强弱具体的含义是什么? 以及为什么要引入弱引用 WeakMap,以及弱引用 WeakMap 的应用

Map 在 JavaScript 中的实现

Map 在 JavaScript 中的实现是采用了 2 数组,一个用于保存 keys,而另一个用于保存 values,在 2 个数组间共享 Map 4 个 API,get、set、has 和 delete。这里拿 set 来说明内部实现的方式,当 set 会同时将一对 value 和 key 分别推到 2 个数组的末端,get 会遍历 keys 数组得到要查询键对应的索引,然后拿索引取 values 中取值。

这样会有两个问题

时间复杂度问题

时间复杂度是 O(n)O(n),其中 n 是 keys 数量,因为查找时会遍历 keys 进行搜索匹配的键值。

内存泄漏问题

对于强类型需要长期在内存中维护 2 个用于 keys 和 values,当其他对象引用清空,因为 Map 会阻止对象被垃圾回收掉,所以可能会造成内存泄漏。

什么又是弱引用

let wm1 = new WeakMap()

wm1.set("title","machine learning")

// Uncaught TypeError: Invalid value used as weak map key

WeakMap 的键的取值不支持基本数据类型,例如 String、Number 等基本数据类型,而支持对象

mw1.set({},"value")

为什么会有这样的设计呢,这里给大家稍作解释。例如我们都需要让数据与 DOM 相关联

const someThingDiv = document.querySelector("#box");
mw1.set({title:"machine learning"},someThingDiv)

可以通过将对象键作为数据,而 DOM 元素作为值,来通过 weakMap 来建立一种数据到 DOM 元素之间的关系。

在 weakMap 中使用一个对象作为键,并且没有其他对这个对象的引用,该对象将会被从内存中自动清除。

let m1 = new Map();

tut_ml = {title:"machine learning"}
tut_dl = {title:"deep learning"}

m1.set(tut_ml,"machine learning tutorial");
m1.set(tut_dl,"deep learning tutorial");

tut_ml = null;

console.log(m1.size) //2

对于强引用 Map 即使 tut_ml 设置为 null ,因为是强引用所以并没有不会从内存清除掉该对象。所以m1输出为

let mw1 = new WeakMap();

tut_ml = {title:"machine learning"}
tut_dl = {title:"deep learning"}
mw1.set(tut_ml,"machine learning tutorial");
mw1.set(tut_dl,"deep learning tutorial");

tut_ml = null

console.log(mw1) //

而对于弱引用当你把键设置为 null 则在 mw1 中对应的 tut_mul 也被从垃圾回收机制给清除了。

应用场景

额外数据

WeakMap 的主要应用场景是 额外数据的存储。假如正在处理一个“属于”另一个文件的一个对象,也可能是第三方库,并想存储一些与之相关的数据,那么这些数据就应该与这个对象共存,这时候就 WeakMap 就派上用场。将这些数据放到 WeakMap 中,并使用该对象作为这些数据的键,那么当该对象被垃圾回收机制回收后,这些数据也会垃圾回收机制清除了。

这个例子是比较经典的 WeakMap 应用的场景,也就是统计来访用户的数量,用户对象作为键而统计用户访问的次数

let visitsCountWeakMap = WeakMap();
function countUser(user){
    let count = visitsCountWeakMap.get(user) || 0;
    visitsCountWeakMap.set(user, count + 1);
}

好处就是当当 user 设置为 null 该用户访问的记录也就自动从 visitsCountWeakMap 中清除了。

let tony = { name: "Tony" };

countUser(tony); // 统计用户访问次数

tony = null;

缓存数据

如果对于用 Map 做缓存<对于多次调用同一个对象,只需在第一次调用时计算出结果,之后的调用可以直从 cache 中获取。不过用 Map 的缺点是不再需要某个对象的时候,需要清理 cache。如果用 WeakMap 替代 Map,这个问题便会消失, 当对象被垃圾回收时,对应的缓存的结果也会被自动地从内存中清除。

let cache = new WeakMap();

// 计算并记结果
function process(obj) {
  if (!cache.has(obj)) {
    let result = obj;

    cache.set(obj, result);
  }

  return cache.get(obj);
}

let tut = {title:'machine learing'};

let result1 = process(tut);
let result2 = process(tut);