在学习缓存函数时,最后提到了WeakMap方式缓存(对入参类型为对象作缓存,而且当对象在WeakMap中的key没有引用时方便浏览器垃圾回收)html
If our parameter were an object (rather than a string, like it is above), we could use WeakMap instead of Map in modern browsers. The benefit of WeakMap is that it would automatically “clean up” the entries when our object key is no longer accessible.
并且JavaScript既然已经有了Map类型的数据结构,为何还有一种叫作WeakMap类型的数据结构呢?它和垃圾回收有什么关系?react
WeakMap很早以前就遇到过,可是没有系统学习过,今天就来对它一探究竟。git
初识WeakMapgithub
弱引用的意义:若是是做为key的对象没有任何地方引用它的话,垃圾收集器(GC)会将其标记为目标而且进行垃圾回收。数组
key:必须是任意object类型(对象、数组、Map、WeakMap等等)
value:any(任意类型,因此也包括undefined,null)浏览器
WeakMap的key是不可枚举的,而Map是可枚举的。
不可枚举就意味着获取不到WeakMap的key列表。缓存
设计为不可枚举的缘由是由于:若是枚举WeakMap的key,那就须要依赖垃圾回收器(GC)的状态,从而引入不肯定性。数据结构
map API在js中能够经过共享4个API(get,set,has,delete)的两个数组来实现:一个存储key,一个存储value。在这个map上设置元素同步推入一个key和一个value到数组尾部。做为结果,key和value的索引会和两个数组绑定起来。从map获取一个值得话,会遍历全部key去找到一个匹配的,而后使用这个匹配到的index从values数组中查询到对应的值。ide
这样实现的话会有2个主要的弊端:函数
相比之下,原生的WeakMap
会保持对key的“弱”引用。原生的WeakMap
不会阻止垃圾回收,最终会移除对key对象的引用。“弱”引用一样可让value很好地垃圾回收。WeakMap特别适用于key映射的信息只有不被垃圾回收时才有价值的场景,换句话说就是WeakMap适用于动态垃圾回收key的场景。
由于引用是弱的,因此WeakMap的键是不能枚举的。没有方法去获取key的列表。若是枚举WeakMap的key,那就须要依赖垃圾回收器(GC)的状态,从而引入不肯定性。若是必需要有key的话,应该去使用Map
。
new WeakMap() new WeakMap(iterable)
其中iterable是数组或者任意能够迭代的对象,须要拥有key-value对(通常是一个二维数组)。null会被当作undefined。
const iterable = [ [{foo:1}, 1], [[1,2,3], 2], [window, 3] ] const iwm = new WeakMap(iterable) // WeakMap {{…} => 1, Window => 3, Array(3) => 2}
WeakMap.prototype.delete(key)
删除key关联的任意值。删除后WeakMap.prototype.has(key)
返回false。
WeakMap.prototype.get(key)
返回与key关联的值,假设不存在关联值得话返回undefined。
WeakMap.prototype.has(key)
返回key在WeakMap上是否存在的结果。
WeakMap.prototype.set(key, value)
在WeakMap对象上为对应key设置指定的value。而且返回WeakMap对象
const wm1 = new WeakMap(), wm2 = new WeakMap(), wm3 = new WeakMap(); const o1 = {}, o2 = function() {}, o3 = window; wm1.set(o1, 37); wm1.set(o2, 'azerty'); wm2.set(o1, o2); // WeakMap的值能够是任意类型,包括object和function wm2.set(o3, undefined); wm2.set(wm1, wm2); // key和value能够是任意对象。包括WeakMap! wm1.get(o2); // "azerty" wm2.get(o2); // undefined, wm2上没有o2这个key wm2.get(o3); // undefined, 由于这是设置的值 wm1.has(o2); // true wm2.has(o2); // false wm2.has(o3); // true (即便value是undefined) wm3.set(o1, 37); wm3.get(o1); // 37 wm1.has(o1); // true wm1.delete(o1); wm1.has(o1); // false
实例和原型链上的数据和方法是公开的,因此能够经过WeakMap类型的私有变量去隐藏实现细节。
const privates = new WeakMap(); function Public() { const me = { // Private data goes here }; privates.set(this, me); } Public.prototype.method = function () { const me = privates.get(this); // Do stuff with private data in `me`... }; module.exports = Public;
class ClearableWeakMap { constructor(init) { this._wm = new WeakMap(init); } clear() { this._wm = new WeakMap(); } delete(k) { return this._wm.delete(k); } get(k) { return this._wm.get(k); } has(k) { return this._wm.has(k); } set(k, v) { this._wm.set(k, v); return this; } }
const key1 = {foo:1}; const key2 = [1,2,3]; const key3 = window; const cwm = new ClearableWeakMap([ [key1, 1], [key2, 2], [key3, 3] ]) cwm.has(key1) // true console.log(cwm);// ClearableWeakMap {_wm: WeakMap {Window => 3, {…} => 1, Array(3) => 2}} cwm.clear(); // 垃圾回收当前WeakMap,而且声称新的空WeakMap cwm.has(key1) // false console.log(cwm);// ClearableWeakMap {_wm: WeakMap {}}
实现缓存函数的方式有不少种,好比单次缓存,Map式全量缓存,LRU最近最少缓存等等。
那么为何还须要WeakMap式的缓存函数呢?这是由于入参为对象类型的缓存且方便浏览器垃圾回收。
function memoizeWeakMap(fn) { const wm = new WeakMap(); return function (arg) { if (wm.has(arg)) { return wm.get(arg); } const cachedArg = arg; const cachedResult = fn(arg); wm.set(cachedArg, cachedResult) console.log('weakmap object', wm) return cachedResult; }; } let testFn = (bar) => {return Object.prototype.toString.call(bar)}; // 这里须要改造一下,改造完返回传入对象的类型 let memoizeWeakMapFn = memoizeWeakMap(testFn); memoizeWeakMapFn(document) // weakmap对document生成缓存 memoizeWeakMapFn([1,2,3]) // weakmap对[1,2,3]生成缓存 memoizeWeakMapFn(function(){}) // weakmap对function(){}生成缓存 memoizeWeakMapFn(new WeakMap()) // weakmap对WeakMap实例生成缓存 memoizeWeakMapFn(new Map()) // weakmap对Map实例生成缓存 memoizeWeakMapFn(new Set()) // weakmap对Set实例生成缓存 WeakMap: 0: {Array(3) => "[object Array]"} 1: {function(){} => "[object Function]"} 2: {WeakMap => "[object WeakMap]"} 3: {Map(0) => "[object Map]"} 4: {#document => "[object HTMLDocument]"} 5: {Set(0) => "[object Set]"}
// 忽略部分代码同上 setTimeout(()=>{ memoizeWeakMapFn(document) },5000)
此时有时最后一次weakmap的打印结果以下:
WeakMap: 0: {#document => "[object HTMLDocument]"}
由于打印时垃圾回收可能并无执行完成,虽然会带来不肯定性,可是能够肯定的是,假设对象没有再被引用,WeakMap中的key会被浏览器自动垃圾回收掉。
这是由于[1,2,3], function(){},new WeakMap(),new Map(),new Set()在后面都没有再继续引用了,并且由于它们做为了WeakMap的key,因此会被浏览器自动垃圾回收掉。
保持一个变量对它的引用。
let memoizeWeakMapFn = memoizeWeakMap(testFn); let retainArray = [1,2,3]; // 保持引用避免被垃圾回收 let retainMap = new Map(); // 保持引用避免被垃圾回收 memoizeWeakMapFn(document) // weakmap对document生成缓存 memoizeWeakMapFn(retainArray) // weakmap对[1,2,3]生成缓存 memoizeWeakMapFn(function(){}) // weakmap对function(){}生成缓存 memoizeWeakMapFn(new WeakMap()) // weakmap对WeakMap实例生成缓存 memoizeWeakMapFn(retainMap) // weakmap对Map实例生成缓存 memoizeWeakMapFn(new Set()) // weakmap对Set实例生成缓存 setTimeout(()=>{ memoizeWeakMapFn(document) },5000)
此时打印结果为:
WeakMap: 0: {#document => "[object HTMLDocument]"} 1: {Map(0) => "[object Map]"} 2: {Array(3) => "[object Array]"}
这是由于[1,2,3], new Map()被变量retainArray和retainMap持续引用着,因此不会被垃圾回收。而function(){},new WeakMap(),new Set()都没有再继续引用了,并且由于它们做为了WeakMap的key,因此会被浏览器自动垃圾回收掉。
能够借助Chrome DevTools的memory面板工具,有一个手动触发垃圾回收的按钮。
// ... setTimeout(()=>{ memoizeWeakMapFn(document) },5000)
好比在上面的例子中,设置了一个5秒的延时:只要代码运行后的5秒内,去手动触发“垃圾回收按钮”,就能够很精确地看到WeakMap的key被垃圾回收了。
固然5秒这个时间是能够人为调整的,保证本身能在setTimeout内的代码运行前触发对WeakMap的垃圾回收便可,能够适当调大。
参考资料:
https://developer.mozilla.org...
https://developer.mozilla.org...
https://fitzgeraldnick.com/20...
https://whatthefuck.is/memoiz...
https://github.com/reactjs/re...