译通过内存快照看WeakMap与三个典型应用?
自 2015 年 6 月 ES2015 发布以来已经过去了很多年。在撰写之前的每周精选时,我在想"是否有我没有使用到的 ES2015 语法,例如 Proxy 或 Reflect? ",首先想到的是 WeakMap 和 WeakSet。因为我对弱引用这个话题比较感兴趣,所以就着这个话题我准备写一篇 WeakMap 的文章来带着大家一起来了解下它。 1.什么是 WeakMap?
WeakMap 对象是键/值对的集合,其中的键被弱引用。 键必须是对象,原始值不能是键。 如果你添加一个原始值作为键,你会得到一个错误 : Uncaught TypeError: Invalid value used as weak map key。
WeakMap 提供了一种检查特定键是否有值的方法,但它没有提供枚举键对象的方法。 众所周知,在 WeakMap 中用作键的对象需要进行垃圾回收。 如果除 WeakMap 外,程序中不存在对对象的引用,则该对象将被垃圾回收。假设有一个使用闭包返回长数组的函数,如下所示: // refs: // https://developer.chrome.com/docs/devtools/memory-problems/heap-snapshots/ function createLargeClosure() { const largeStr = new Array(1000000).join("x"); const lc = function largeClosure() { return largeStr; }; return lc; }
首先,使用 Map 创建一个内存泄漏示例: const map = new Map(); function start() { const timer = setInterval(() => { const lc = createLargeClosure(); // 函数 map.set(lc, "##"); }, 1000); setTimeout(function () { clearInterval(timer); }, 5001); }
使用 DevTools 检查内存分配状态:
现在,如果用 new WeakMap() 而不是 new Map() 替换集合对象后运行相同的代码,可以看到内存被释放。
如果您因为不熟悉 DevTools 而对这个快照不熟悉,建议您观看"Chrome DevTools 详解"或阅读 Chrome 文档,统一提供在参考文献中。 2.WeakMap 构造函数的特点WeakMap 是全局对象的一个属性 不能作为通用函数调用,作为通用函数调用时会报错 必须使用 new 关键字调用构造函数。 它有一个内部插槽 [[Prototype]],它的值是 Function.prototype WeakMap .prototype.constructor.proto === Function .prototype;它可以通过 extends 关键字用于继承。 3.WeakMap 方法
WeakMap 只提供四种方法,下面会讲到。 如前所述,不支持枚举键相关的方法。 根据 ECMAScript 规范,WeakMap 的实现在键/值对变得不可访问和键/值对从 WeakMap 中删除之间可能会有延迟。 因此,无法执行涉及枚举整个键/值对的操作。 3.1 WeakMap.prototype.delete(key)
删除映射到键的所有值, WeakMap.prototype.has(key) 之后会返回 false。 3.2 WeakMap.prototype.get(key)
返回映射到 key 的值,如果没有映射值则返回未定义。 3.3 WeakMap.prototype.has(key)
返回一个布尔值,表示映射到 WeakMap 实例对象中的键的值是否存在。 3.4 WeakMap.prototype.set(key, value)
在 WeakMap 实例对象中设置映射到 key 的值(value),返回 WeakMap 实例对象。 3.5 WeakMap.prototype.clear()
由于安全问题,除了 IE 之外的所有浏览器都弃用了 clear() 方法。当前版本或者起草中没有这个方法,这个方法在版本 28(2014 年 10 月 14) 之前是 ECMAScript 6 起草规范的一部分,但是在起草之后的版本中被移除了。它不在是最终标准的一部分了 。 4.WeakMap 示例
WeakMap 的简单介绍到此结束,接下来看几个例子熟悉下如何使用它。通常,WeakMap 用于容易发生内存泄漏的代码。 4.1 Caching(缓存)
WeakMap 有助于数据存储(memoization),数据存储(memoization)是一种缓存昂贵计算的结果并在接收到相同输入值时返回缓存结果的技术。 function createLargeClosure() { const largeObj = { a: 1, b: 2, str: new Array(1000000).join("x"), }; const lc = function largeClosure() { return largeObj; }; return lc; } const memo = new WeakMap(); function memoize(obj) { if (memo.has(obj)) { console.log("Get cached result"); return memo.get(obj); } const compute = obj.a + obj.b; console.log("Set computed result to caching map"); memo.set(obj, compute); return compute; } function start() { const lcObj = createLargeClosure(); // 返回一个函数 const timer = setInterval(() => { memoize(lcObj()); // 返回大对象作为key }, 1000); setTimeout(function () { clearInterval(timer); }, 5001); // 清除定时器不再持有对key的引用 }
如果使用 Map 创建 memo,则使用 DevTools 检查的内存分配状态如下:
如果将缓存对象从 Map 更改为 WeakMap,可以看到上图中的对象已被删除,并且随着内存被释放,上面条形图中的蓝色量表略有下降。
4.2 自定义事件
在某些项目中,通常需要自定义事件对象。 有一种方法可以将事件对象混合到实例中,例如 TOAST UI 的代码片段(查看参考资料)。 但是,如果您只是将事件对象实现为单例,WeakMap 可以用于解决内存泄漏。 以下代码注册了一个以 targetObject 为键的事件处理程序并执行它: class EventEmitter { constructor() { this.targets = new WeakMap(); } on(targetObject, handlers) { if (!this.targets.has(targetObject)) { this.targets.set(targetObject, {}); } const targetHandlers = this.targets.get(targetObject); Object.keys(handlers).forEach((handlerName) => { targetHandlers[handlerName] = targetHandlers[handlerName] || []; targetHandlers[handlerName].push( handlers[handlerName].bind(targetObject) ); }); } fire(targetObject, handlerName, args) { const targetHandlers = this.targets.get(targetObject); if (targetHandlers && targetHandlers[handlerName]) { targetHandlers[handlerName].forEach((handler) => handler(args)); } } }
当执行下面的代码时,user 和 handlers 对象的内存会自动被垃圾收集器收集,因为 user 在 WeakMap 中注册为键。 const emitter = new EventEmitter(); function start() { const user = { name: "John", }; const handlers = { sayHello: function () { console.log(`Hello, my name is ${this.name}`); }, sayGoodBye: function () { console.log(`Good bye, my name is ${this.name}`); }, }; emitter.on(user, handlers); // 以对象为key const timer = setInterval(() => { emitter.fire(user, "sayHello"); emitter.fire(user, "sayGoodBye"); }, 1000); // 清除定时器 setTimeout(function () { clearInterval(timer); }, 5001); }
下图展示了用 Map 实现 EventEmitter 时,user 和 handlers 没有被收集的情况。
如果使用 Wea kMap,我们可以看到对象已被垃圾收集器收集。
4.3 私有属性
私有属性可以很容易地在 Babel 的转译结果中找到,以下代码使用 # 关键字指定访问修饰符。 class A { #privateFieldA = 1; #privateFieldB = "A"; }
下面显示了转译后的实际结果。您可以在 Babel 的 Try it out(链接)中测试此代码。 "use strict"; var _privateFieldA = /*#__PURE__*/ new WeakMap(); var _privateFieldB = /*#__PURE__*/ new WeakMap(); class A { constructor() { _privateFieldA.set(this, { writable: true, value: 1, }); _privateFieldB.set(this, { writable: true, value: "A", }); } }
私有字段使用 WeakMap 的原因如下: 在信息隐藏方面,只有当您知道 WeakMap 实例和类 A 的实例时,才可以使用一个值 在防止内存泄漏方面,如果 A 类的实例引用除了 WeakMap 实例外不存在,则自动回收内存 5.总结
综上所述,WeakMap 是一种通过保留弱引用而在内存泄漏管理方面具有优势的数据结构,尽管它不支持像 Map 那样提供枚举的方法。
除了用于 Vue 3 响应式之外,WeakMap 还用于如何在 lodash 中使用 _.memoize 的示例,这表明可以将缓存对象更改为 WeakMap。它还用于填充私有访问修饰符以隐藏信息。
诚然,现在大部分项目都是基于框架实现的,一般情况下不会经常用到 WeakMap。但是,如果您需要一个以项目中的对象作为标识符的数据结构,您可能需要考虑在使用常规对象或 Map 之前使用 Wea kMap。 参考资料
https://toastui.medium.com/lets-find-out-about-weakmap-2150905935d1
https://youtu.be/cAIo4dEEPuc?t=1667 https://developer.chrome.com/docs/devtools/memory-problems/heap-snapshots/
https://exploringjs.com/es6/ch_classes.html#sec_private-data-via-weakmaps
https://babeljs.io/repl
https://github.com/nhn/tui.code-snippet/blob/master/customEvents/customEvents.js
河南省印发物流十四五规划如何从大做强?规划提到这些目标物流仓库顶端新闻河南商报首席记者吴军文图十四五期间,河南物流行业会往哪方面发展?物流企业该如何发力?2022年4月18日,顶端新闻河南商报记者获悉,河南省人民政府近日印发的河南省十
被迫转型,海天味业难以扭转困局编辑于斌出品潮起网于见专栏民以食为天。作为柴米油盐酱醋茶中不可或缺的部分,酱油等调味料的对于普罗大众,需求也十分刚性。而在调味品这个看似无比狭窄的赛道,也跑出了千亿市值的独角兽海天
新生儿起名字大全,大吉大利的好名字好听的男孩名字博睿泽舟奕鸣子航文翰翰星晨轩琪韬华灿润哲敬华鹏池智龙运莱子博文星翰州子硕华晨子顺秋扬建东世信明琪夏清志佑坤林家凯善祥明轩真华信卓航勋怀仁朴尘家启轩逸云泰正浩宁德家赫文
100元人民币在越南能够干啥?在越南的中国游客意料之中100元能在越南享受哪些服务?越南中国游客表示,人民币的强大,出乎意料。(此处已添加小程序,请到今日头条客户端查看)现在越来越多中国游客开始出国旅行,在放松身心的同时也能了解其他国
春天来了,复活节也来了春日记分享你所在城市的美景今天天气格外的晴朗,此刻的温度已经达到了20度,今天是复活节星期一。因为是公共假日,商店依然关门,大家依然在家休息。除了餐饮业,公共假日的到来让人们有了庆
一季度国内旅游总人次8。30亿比上年同期下降19。0中新网4月18日电文化和旅游部18日在其官网发布2022年一季度国内旅游数据情况。根据国内旅游抽样调查统计结果,2022年一季度,国内旅游总人次8。30亿,比上年同期减少1。94亿
人类史上徒步环球第一人潘德明,罗斯福送他金牌,张学良为他题词环球旅行,简简单单的四个字,却是不少人的梦想。世界那么大,大家都渴望能走出去大开眼界。但环球旅行,对百分之九十九的人而言,都是遥不可及的梦。不过,就是这个看似遥不可及的梦,早在上个
600光年外的超级地球,可以实现人类的星际移民吗?你有没有想过逃离地球,听说过超级地球吗?相信大家对这个词很熟悉,但是对于这个星球本身一定是非常陌生的。今天我们就来一起了解一下这个超级地球,看看能称得上超级地球的星球到底什么样子。
龙高云雾龙高山云遮雾绕。陈凤盾摄钟友梅秋日,好友约我同游龙高山,恰逢我休假,便欣然同往。她阅历丰富,对龙高山美景也非常熟悉,一路上有她做导游,真是受益匪浅。于是,我与那山那海那云那雾,便有
非洲花草繁盛,却只有一个城市被称为花城印象最深的,当数那里异树奇花种类繁多,市内片片葱绿,热带风光旖旎。高大挺拔的棕榈树,金光灿烂的凤凰树,殷红富丽的三角花树,纤秀玲珑的鸡蛋花树,五彩斑斓的紫串花黄串花和红串花树,以及
神舟十三号成功着陆移动圆满完成通信保障神舟十三号飞船于2021年10月16日在酒泉卫星中心发射,搭载翟志刚王亚平叶光富三名航天员开启了长达183天的太空之旅,创造了中国航天员连续再贵飞行时间的最长记录。神舟十三号载人飞