首页 > 其他分享 >如何理解WeakMap?

如何理解WeakMap?

时间:2022-10-07 23:01:08浏览次数:65  
标签:缓存 WeakMap 回收 如何 理解 垃圾 key new

如何理解WeakMap?_缓存

在学习​​缓存函数​​时,最后提到了WeakMap方式缓存(对入参类型为对象做缓存,并且当对象在WeakMap中的key没有引用时方便浏览器垃圾回收)

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类型的数据结构呢?它和垃圾回收有什么关系?

WeakMap很早之前就遇到过,但是没有系统学习过,今天就来对它一探究竟。

  • 初识WeakMap
  • WeakMap的key为什么是弱引用的?
  • WeakMap与Map最大的不同
  • 新增WeakMap类型是为什么?
  • WeakMap的实例方法
  • WeakMap最简使用方式
  • WeakMap存储私有数据
  • 拥有clear方式的WeakMap类
  • WeakMap式自动垃圾回收缓存函数
  • 参考资料

初识WeakMap

  • WeakMap对象是一组键值对的集合,其中key是弱引用的
  • WeakMap的key必须是对象类型,value可以是任意类型

WeakMap的key为什么是弱引用的?

弱引用的意义:如果是作为key的对象没有任何地方引用它的话,垃圾收集器(GC)会将其标记为目标并且进行垃圾回收

WeakMap的key和value可以是哪些类型

key:必须是任意object类型(对象、数组、Map、WeakMap等等) value:any(任意类型,所以也包括undefined,null)

WeakMap与Map最大的不同

WeakMap的key是不可枚举的,而Map是可枚举的。 不可枚举就意味着获取不到WeakMap的key列表。

设计为不可枚举的原因是因为:如果枚举WeakMap的key,那就需要依赖垃圾回收器(GC)的状态,从而引入不确定性。

新增WeakMap类型是为什么?

map API在js中可以通过共享4个API(get,set,has,delete)的两个数组来实现:一个存储key,一个存储value。在这个map上设置元素同步推入一个key和一个value到数组尾部。作为结果,key和value的索引会和两个数组绑定起来。从map获取一个值得话,会遍历所有key去找到一个匹配的,然后使用这个匹配到的index从values数组中查询到对应的值。

这样实现的话会有2个主要的弊端:

  1. 首先是set和search的时间复杂度是O(n),n是map中key数组的数量,因为都需要遍历列表去查找到需要的值
  2. 其次是会造成内存泄漏,因为数组需要无期限地去确保每个key和每个value的引用。这些引用会导致阻止key被垃圾回收掉,即使这个对象没有任何地方再引用到了,key对应的value也同样会被阻止垃圾回收。

相比之下,原生的​​WeakMap​​​会保持对key的“弱”引用。原生的​​WeakMap​​不会阻止垃圾回收,最终会移除对key对象的引用。“弱”引用同样可以让value很好地垃圾回收。WeakMap特别适用于key映射的信息只有不被垃圾回收时才有价值的场景,换句话说就是WeakMap适用于动态垃圾回收key的场景。

因为引用是弱的,所以WeakMap的键是不能枚举的。没有方法去获取key的列表。如果枚举WeakMap的key,那就需要依赖垃圾回收器(GC)的状态,从而引入不确定性。如果必须要有key的话,应该去使用​​Map​​。

WeakMap的基本概念

语法

new WeakMap()
new WeakMap(iterable)

其中iterable是数组或者任意可以迭代的对象,需要拥有key-value对(一般是一个二维数组)。null会被当做undefined。

iterable为二维数组
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对象

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存储私有数据

实例和原型链上的数据和方法是公开的,所以可以通过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;

拥有clear方式的WeakMap类

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 {}}

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]"}

如何体现出WeakMap的垃圾回收特性呢

// 忽略部分代码同上
setTimeout(()=>{
memoizeWeakMapFn(document)
},5000)

此时有时最后一次weakmap的打印结果如下:

WeakMap:
0: {#document => "[object HTMLDocument]"}
为什么说是“有时”?

因为打印时垃圾回收可能并没有执行完成,虽然会带来不确定性,但是可以确定的是,假设对象没有再被引用,WeakMap中的key会被浏览器自动垃圾回收掉。

为什么weakmap中仅保存了document?

这是因为[1,2,3], function(){},new WeakMap(),new Map(),new Set()在后面都没有再继续引用了,而且因为它们作为了WeakMap的key,所以会被浏览器自动垃圾回收掉。

如何不让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面板工具,有一个手动触发垃圾回收的按钮。

如何理解WeakMap?_JavaScript_02

// ...
setTimeout(()=>{
memoizeWeakMapFn(document)
},5000)

比如在上面的例子中,设置了一个5秒的延时:只要代码运行后的5秒内,去手动触发“垃圾回收按钮”,就可以很精确地看到WeakMap的key被垃圾回收了。

当然5秒这个时间是可以人为调整的,保证自己能在setTimeout内的代码运行前触发对WeakMap的垃圾回收即可,可以适当调大。

参考资料:

​developer.mozilla.org/en-US/docs/…​

​developer.mozilla.org/en-US/docs/…​

​fitzgeraldnick.com/2014/01/13/…​

​whatthefuck.is/memoization​

​github.com/reactjs/rea…​

  • 微信公众号: 大大大前端

标签:缓存,WeakMap,回收,如何,理解,垃圾,key,new
From: https://blog.51cto.com/u_15725382/5735214

相关文章

  • 深入理解JSON.stringify()
    就我目前4年(实习了1年,965了1年,996了2年,算3年感觉少了,说是4年老司机也不为过吧。)的工作经验来看,JSON.stringify一般有以下用途:深拷贝:深拷贝引用类型的数据序列化:服务端存储......
  • java中,如何解决@NotBlank不生效的问题 @NotBlank @NotEmpty不生效,以及对象嵌套问题
    这篇文章主要介绍了如何解决@NotBlank不生效的问题,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教。1.解决@NotBlank不生效最近做一个新......
  • 如何将String类的的ids转换成list集合
    publicintdeleteSchoolCalendarDetailByIds(Stringids){List<String>list=newArrayList<>();Stringstr[]=ids.split(",");list=Arrays.a......
  • 如何选到一位靠谱的研究生导师
    有师弟在知乎找到我、和我咨询一下选导师的事情因此、突发奇想、产出、总结了本篇博文敬请查阅????早起的鸟儿有虫吃不同阶段的同学,选到一位好导师、一位适合自己的导师的......
  • 快速理解memset
    memset函数是在头文件:cstring 或 memory中 memset函数的作用是将数字以单个字节逐个拷贝的方式放到指定的内存中去memset(a,0,sizeofa);int类型的变量一般占......
  • Android国标接入端如何播放GB28181平台端语音广播数据
    GB28181语音广播这块,我们依据GB/T28181-2016针对流程和实例代码,做过详细的描述,本次主要是探讨下,广播数据过来后,如何处理。鉴于我们之前有非常成熟的RTMP|RTSP低延迟播放模块......
  • 如何在 CentOS 及其衍生版上安装 ONLYOFFICE 文档 v7.2
     使用社区版,您可以在本地服务器上安装 ONLYOFFICE文档,并将在线编辑器与 ​​ONLYOFFICE协作平台​​或​​其他热门系统​​集成在一起。ONLYOFFICE文档是什么ONLYOFFI......
  • 前端妹子问我 position fixed 失效问题该如何解决?
    背景这两天公司一位妹子问我,“我这边调试的时候本地显示没问题,到手机端就有问题,该怎么办呢?”测试环境没问题到线上就有问题了?对此我也很纳闷。下图是复现的效果图,这个是一......
  • 如何快速在团队内做一次技术分享?
    前言相信很多小伙伴跟我一样,是一位奋斗在一线的业务开发,每天有做不完的任务,还有项目经理在你耳边催你,“这个功能今天能完成吗?”其实作为一名前端工程师,任务就是完成Leader......
  • 玻璃拟态是什么?前端该如何实现
    你好,玻璃拟态玻璃拟态是目前市面上的新风格,越来越受欢迎。新拟态(Neumorphism)模仿了受到挤压的塑料材质(凹凸质感,凸显层次感),这个新的视觉风格更加注重垂直空间z轴的使用。它......