1. 前言
前面我们已经学了 Set 和对应的 WeakSet,Map 对应也有 WeakMap,在学习 WeakSet 时我们已经接触到弱引的相关知识,本节我们将结合 WeakMap 深入的理解弱引用的相关问题。
由前面学到的 Set 和 WeakSet 具有很多相似的地方,比如它们存放的都是独一无二的元素。所以,对 Map 和 WeakMap 也可以进行类比,WeakMap 中也存放的是键值对。不同的是 WeakMap 的 key 只能是对象,值可以是任意类型的,和 WeakSet 一样 WeakMap 对 key 的引用是弱引用。
2. WeakMap 基本用法
WeakMap 像 Map 一样可以接受一个二维数组进行初始化。
var wm = new WeakMap([ [{name: 'mybj'}, 'mybj'], [{name: 'lesson'}, 'ES6'] ]) console.log(wm)
上面的代码打印结果如下:
从打印的结果可以大概了解 WeakMap 的存储方式,WeakMap 的实例本来就是一个对象。
WeakMap 只提供了四个方法用于操作数据。
方法名 | 描述 |
---|---|
set | 接收键值对,向 WeakMap 实例中添加元素 |
get | 传入指定的 key 获取 WeakMap 实例上的值 |
has | 传入指定的 key 查找在 WeakMap 实例中是否存在 |
delete | 传入指定的 key 删除 WeakMap 实例中对应的值 |
看如下实例:
var wm1 = new WeakMap(); var wm2 = new WeakMap(); var wm3 = new WeakMap(); var o1 = {name: 'imooc'}; var o2 = function(){}; var o3 = window; // 使用 set 方法添加元素,value 可以是任意值,包括对象、函数甚至另外一个 WeakMap 对象 wm1.set(o1, 'ES6 Wiki'); wm1.set(o2, 10); wm2.set(o1, o2); wm2.set(o3, null); wm2.set(wm1, wm2); wm1.get(o2); // 10 wm2.get(o2); // undefined,wm2 中没有 o2 这个键 wm2.get(o3); // null wm1.has(o2); // true wm2.has(o2); // false wm2.has(o3); // true (即使值是 null) wm3.set(o1, 'lesson is ES6!'); wm3.get(o1); // lesson is ES6! wm1.has(o1); // true wm1.delete(o1); wm1.has(o1); // false
上面的实例基本涵盖了 WeakMap 四种方法的基本使用情况,上面也提到了 WeakMap 的 key 只能是对象类型的,如果 WeakMap 的 key 是基本类型数据时就会报错。
var wm = new WeakMap(); wm.set('lesson', 'ES6'); // Uncaught TypeError: Invalid 错误 value used as weak map key
上面代码中在设置 wm 值时,报错了。从报错类型知道是一个类型错误,弱引用映射的键是无效的。
3. WeakMap 使用场景
上节我们学习了 Map 的使用,在 JavaScript 中对对象的引用都是强保留的,这意味着只要持有该对象的引用,垃圾回收机制就不会回收该对象。
var obj = {a: 10, b: 88};
上面是一个字面量对象,只要我们访问 obj 对象,或者任何地方有引用该对象,这个对象就不会被垃圾回收。而在 ES6 之前 JavaScript 中没有弱引用概念,弱引用的本质上就是不会影响垃圾回收机制。其实,WeakMap 并不是真正意义上的弱引用,只要键仍然存在,它就强引用其上的内容。WeakMap 仅在键被垃圾回收之后,才弱引用它的内容,所以也不用太纠结其中的弱。
在官方上对为什么使用 WeakMap 做了描述,Map 在存储值是有顺序的,这种顺序是通过二维数组的形式来完成的。我们知道 Map 在初始化时接受一个数组,数组中的每一项也是一个数组,这个数组中包含两个值,一个存放的是键,一个存放的是值。新添加的值会添加到数组的末尾,从而使得键值具有索引的含义。在取值时就需要进行遍历,通过索引取出对应的值。
但是这样存在两个很大的缺陷:
- 赋值和搜索的时间复杂度都是 O (n) (n 是键值对的个数),因为这两个操作都是要遍历整个数组才能完成的;
- 可能会导致内存泄漏,因为数组会一直引用每个键和值。这种引用使得垃圾回收算法不能回收处理它们,即使没有任何引用存在。
相比之下,原生的 WeakMap 持有的是 “弱引用”,这意味着它不会影响垃圾回收。WeakMap 中的 key 只有在键值存在的情况才会引用,而且只是一个读取操作,并不会对引用的值产生影响。也正因为这样的弱引用关系,导致 WeakMap 中的 key 是不可枚举的,假设 key 是可枚举的,就会对该值产生引用关系,影响垃圾回收。
如果只是单纯地向对象上添加值用于检查某些逻辑判断,又不想影响垃圾回收机制,这个时候就可以使用 WeakMap。这里说一点,在一些框架中已经使用了像 WeakMap 和 WeakSet 这样的数据结构,其中 Vue3 就引入了这样的新数据进行一些必要的逻辑判断,有兴趣的可以去扒扒 Vue3 的源码研究研究。
4. 总结
本节主要介绍了 WeakMap 的使用和应用场景,这里要说明的一点是:WeakMap 不算真正意义上的弱引用方式,只要键仍然存在,它就强引用其上的内容。最新的 ES 方案提出了 WeakRef 的 API 作为真正的弱引用方式,现在还处于不稳定期间,也还存在一些问题,如果有兴趣的可以研究一下。最后,在 WeakMap 的使用上,大多数都是用来进行一些必要的逻辑判断的。在 WeakMap 实例上添加一个对已知对象的引用,从而在需要使用时,对该对象进行必要的逻辑判断。