自2015年6月ES2015发布以来已经过去了很多年。在撰写之前的每周精选时,我在想是否有我没有使用到的ES2015语法,例如Proxy或Reflect?,首先想到的是WeakMap和WeakSet。因为我对弱引用这个话题比较感兴趣,所以就着这个话题我准备写一篇WeakMap的文章来带着大家一起来了解下它。1。什么是WeakMap? WeakMap对象是键值对的集合,其中的键被弱引用。键必须是对象,原始值不能是键。如果你添加一个原始值作为键,你会得到一个错误:UncaughtTypeError:Invalidvalueusedasweakmapkey。 WeakMap提供了一种检查特定键是否有值的方法,但它没有提供枚举键对象的方法。众所周知,在WeakMap中用作键的对象需要进行垃圾回收。如果除WeakMap外,程序中不存在对对象的引用,则该对象将被垃圾回收。假设有一个使用闭包返回长数组的函数,如下所示:refs:https:developer。chrome。comdocsdevtoolsmemoryproblemsheapsnapshotsfunctioncreateLargeClosure(){constlargeStrnewArray(1000000)。join(x);constlcfunctionlargeClosure(){returnlargeStr;};returnlc;} 首先,使用Map创建一个内存泄漏示例:constmapnewMap();functionstart(){consttimersetInterval((){constlccreateLargeClosure();函数map。set(lc,);},1000);setTimeout(function(){clearInterval(timer);},5001);} 使用DevTools检查内存分配状态: 现在,如果用newWeakMap()而不是newMap()替换集合对象后运行相同的代码,可以看到内存被释放。 如果您因为不熟悉DevTools而对这个快照不熟悉,建议您观看ChromeDevTools详解或阅读Chrome文档,统一提供在参考文献中。2。WeakMap构造函数的特点WeakMap是全局对象的一个属性不能作为通用函数调用,作为通用函数调用时会报错必须使用new关键字调用构造函数。它有一个内部插槽〔〔Prototype〕〕,它的值是Function。prototypeWeakMap。prototype。constructor。protoFunction。prototype;它可以通过extends关键字用于继承。3。WeakMap方法 WeakMap只提供四种方法,下面会讲到。如前所述,不支持枚举键相关的方法。根据ECMAScript规范,WeakMap的实现在键值对变得不可访问和键值对从WeakMap中删除之间可能会有延迟。因此,无法执行涉及枚举整个键值对的操作。3。1WeakMap。prototype。delete(key) 删除映射到键的所有值,WeakMap。prototype。has(key)之后会返回false。3。2WeakMap。prototype。get(key) 返回映射到key的值,如果没有映射值则返回未定义。3。3WeakMap。prototype。has(key) 返回一个布尔值,表示映射到WeakMap实例对象中的键的值是否存在。3。4WeakMap。prototype。set(key,value) 在WeakMap实例对象中设置映射到key的值(value),返回WeakMap实例对象。3。5WeakMap。prototype。clear() 由于安全问题,除了IE之外的所有浏览器都弃用了clear()方法。当前版本或者起草中没有这个方法,这个方法在版本28(2014年10月14)之前是ECMAScript6起草规范的一部分,但是在起草之后的版本中被移除了。它不在是最终标准的一部分了。4。WeakMap示例 WeakMap的简单介绍到此结束,接下来看几个例子熟悉下如何使用它。通常,WeakMap用于容易发生内存泄漏的代码。4。1Caching(缓存) WeakMap有助于数据存储(memoization),数据存储(memoization)是一种缓存昂贵计算的结果并在接收到相同输入值时返回缓存结果的技术。functioncreateLargeClosure(){constlargeObj{a:1,b:2,str:newArray(1000000)。join(x),};constlcfunctionlargeClosure(){returnlargeObj;};returnlc;}constmemonewWeakMap();functionmemoize(obj){if(memo。has(obj)){console。log(Getcachedresult);returnmemo。get(obj);}constcomputeobj。aobj。b;console。log(Setcomputedresulttocachingmap);memo。set(obj,compute);returncompute;}functionstart(){constlcObjcreateLargeClosure();返回一个函数consttimersetInterval((){memoize(lcObj());返回大对象作为key},1000);setTimeout(function(){clearInterval(timer);},5001);清除定时器不再持有对key的引用} 如果使用Map创建memo,则使用DevTools检查的内存分配状态如下: 如果将缓存对象从Map更改为WeakMap,可以看到上图中的对象已被删除,并且随着内存被释放,上面条形图中的蓝色量表略有下降。 4。2自定义事件 在某些项目中,通常需要自定义事件对象。有一种方法可以将事件对象混合到实例中,例如TOASTUI的代码片段(查看参考资料)。但是,如果您只是将事件对象实现为单例,WeakMap可以用于解决内存泄漏。以下代码注册了一个以targetObject为键的事件处理程序并执行它:classEventEmitter{constructor(){this。targetsnewWeakMap();}on(targetObject,handlers){if(!this。targets。has(targetObject)){this。targets。set(targetObject,{});}consttargetHandlersthis。targets。get(targetObject);Object。keys(handlers)。forEach((handlerName){targetHandlers〔handlerName〕targetHandlers〔handlerName〕〔〕;targetHandlers〔handlerName〕。push(handlers〔handlerName〕。bind(targetObject));});}fire(targetObject,handlerName,args){consttargetHandlersthis。targets。get(targetObject);if(targetHandlerstargetHandlers〔handlerName〕){targetHandlers〔handlerName〕。forEach((handler)handler(args));}}} 当执行下面的代码时,user和handlers对象的内存会自动被垃圾收集器收集,因为user在WeakMap中注册为键。constemitternewEventEmitter();functionstart(){constuser{name:John,};consthandlers{sayHello:function(){console。log(Hello,mynameis{this。name});},sayGoodBye:function(){console。log(Goodbye,mynameis{this。name});},};emitter。on(user,handlers);以对象为keyconsttimersetInterval((){emitter。fire(user,sayHello);emitter。fire(user,sayGoodBye);},1000);清除定时器setTimeout(function(){clearInterval(timer);},5001);} 下图展示了用Map实现EventEmitter时,user和handlers没有被收集的情况。 如果使用WeakMap,我们可以看到对象已被垃圾收集器收集。 4。3私有属性 私有属性可以很容易地在Babel的转译结果中找到,以下代码使用关键字指定访问修饰符。classA{privateFieldA1;privateFieldBA;} 下面显示了转译后的实际结果。您可以在Babel的Tryitout(链接)中测试此代码。usestrict;varprivateFieldAPUREnewWeakMap();varprivateFieldBPUREnewWeakMap();classA{constructor(){privateFieldA。set(this,{writable:true,value:1,});privateFieldB。set(this,{writable:true,value:A,});}} 私有字段使用WeakMap的原因如下:在信息隐藏方面,只有当您知道WeakMap实例和类A的实例时,才可以使用一个值在防止内存泄漏方面,如果A类的实例引用除了WeakMap实例外不存在,则自动回收内存5。总结 综上所述,WeakMap是一种通过保留弱引用而在内存泄漏管理方面具有优势的数据结构,尽管它不支持像Map那样提供枚举的方法。 除了用于Vue3响应式之外,WeakMap还用于如何在lodash中使用。memoize的示例,这表明可以将缓存对象更改为WeakMap。它还用于填充私有访问修饰符以隐藏信息。 诚然,现在大部分项目都是基于框架实现的,一般情况下不会经常用到WeakMap。但是,如果您需要一个以项目中的对象作为标识符的数据结构,您可能需要考虑在使用常规对象或Map之前使用WeakMap。参考资料 https:toastui。medium。comletsfindoutaboutweakmap2150905935d1 https:youtu。becAIo4dEEPuc?t1667https:developer。chrome。comdocsdevtoolsmemoryproblemsheapsnapshots https:exploringjs。comes6chclasses。htmlsecprivatedataviaweakmaps https:babeljs。iorepl https:github。comnhntui。codesnippetblobmastercustomEventscustomEvents。js