529

的实际用途是什么WeakMap(弱点地图)ECMAScript 6中引入的数据结构?

由于弱映射的键创建了对其相应值的强引用,因此确保插入到弱映射中的值将从未只要它的键仍然存在就消失,它不能用于备注表、缓存或任何其他通常使用弱引用、弱值映射等的对象。

在我看来:

weakmap.set(键,值);

……只是一种迂回的说法:

key.value=值;

我遗漏了哪些具体的用例?

16

9个答案9

重置为默认值
664

基本上

WeakMaps提供了一种从外部扩展对象的方法,而不会干扰垃圾收集。只要您想扩展一个对象,但由于它是密封的或来自外部源而无法扩展,就可以应用WeakMap。

WeakMap是一个地图(字典),其中钥匙弱-即,如果所有对钥匙丢失,并且不再引用值-价值可以进行垃圾收集。让我们先通过示例来展示这一点,然后稍作解释,最后以实际使用结束。

假设我使用的API为我提供了一个特定对象:

var obj=获取对象源库();

现在,我有一个使用对象的方法:

函数useObj(obj){用(obj)做某事;}

我想跟踪某个对象调用该方法的次数,并报告是否超过N次。天真地认为使用地图:

var-map=新map();//贴图可以有对象关键点函数useObj(obj){用(obj)做某事;var调用=map.get(obj)|0;调用++;//又打了一次电话if(调用>10)report();//报告调用超过10次map.set(对象,称为);}

这是可行的,但它有一个内存泄漏——我们现在跟踪传递给函数的每个库对象,从而防止库对象被垃圾收集。相反,我们可以使用弱点地图:

var-map=新的WeakMap();//创建弱映射函数useObj(obj){用(obj)做某事;var调用=map.get(obj)||0;调用++;//又打了一次电话if(调用>10)report();//报告调用超过10次map.set(对象,称为);}

内存泄漏消失了。

用例

一些用例可能会导致内存泄漏,并由启用WeakMap(弱点地图)包括:

  • 保留特定对象的私有数据,并且仅允许引用地图的人访问该数据。随着私有符号提案的提出,一种更为特殊的方法也随之而来,但这距离现在还有很长一段时间。
  • 保留有关库对象的数据,而不更改它们或产生开销。
  • 保存关于一小组对象的数据,其中存在许多此类对象,以避免JS引擎用于相同类型对象的隐藏类出现问题。
  • 在浏览器中保存有关DOM节点等宿主对象的数据。
  • 从外部向对象添加功能(如另一个答案中的事件发射器示例)。

让我们看看真正的用途

它可以用于从外部延伸对象。让我们从Node.js的真实世界中给出一个实用的(经过改编的,有点真实的)示例。

假设你是Node.js承诺对象-现在您想跟踪所有当前被拒绝的承诺-然而,您确实要这样做希望在不存在对它们的引用的情况下防止它们被垃圾收集。

现在,你不要出于明显的原因,您希望向本机对象添加属性,所以您遇到了困难。如果保留对承诺的引用,则会导致内存泄漏,因为不会发生垃圾回收。如果你不保留推荐信,那么你就无法保存关于个人承诺的额外信息。任何涉及保存承诺ID的方案都意味着您需要引用它。

输入WeakMaps

WeakMaps意味着钥匙都很弱。无法枚举弱映射或获取其所有值。在弱映射中,可以基于键存储数据,当键被垃圾收集时,也可以存储值。

这意味着,如果您承诺可以存储它的状态,那么该对象仍然可以被垃圾收集。稍后,如果您得到一个对象的引用,您可以检查是否有任何与它相关的状态并报告它。

这用于实施未处理的拒绝挂钩佩特卡·安东诺夫:

process.on('unhandledRejection',函数(reason,p){console.log(“Unhandled Rejection at:Promise”,p,“reason:”,reason);//特定于应用程序的日志记录、引发错误或其他逻辑});

我们将有关承诺的信息保存在地图中,并可以知道何时处理了被拒绝的承诺。

11
  • 15
    你好!你能告诉我示例代码的哪一部分导致内存泄漏吗?
    – 意大利
    评论 2016年10月28日13:17
  • 29
    @ltamajs4当然,在使用对象示例使用地图而不是WeakMap(弱点地图)我们使用传入对象作为映射键。该对象从未从映射中删除过(因为我们不知道何时删除),因此始终存在对它的引用,并且它永远不会被垃圾收集。在WeakMap示例中,只要对象的所有其他引用都消失了,就可以从WeakMap(弱点地图).如果你仍然不确定我的意思,请告诉我 评论 2016年10月28日13:56
  • @Benjamin,我们需要区分对内存敏感缓存的需要和对data_object元组的需要。不要将这两个单独的要求混为一谈。您的打电话示例最好使用jsfiddle.net/f2efbm7z它并没有证明弱映射的使用。事实上,它可以更好地写在总共6方法,我将在下面列出。 评论 2017年9月18日18:26
  • 1
    从根本上讲,弱映射的用途是一个内存敏感缓存。虽然它可以用于从外部延伸对象,这是一个不必要的糟糕黑客,绝对不是它的正确目的. 评论 2017年9月18日18:27
  • 1
    回复“让我们看看真正的用途。。。在地图中保留有关承诺的信息“,我不明白为什么承诺例外地图需要弱:为什么不在触发“rejectionHandled”后手动删除映射中的条目?这是一个更好的方法。 评论 2017年9月18日18:32
50

这个答案似乎有偏见,在现实世界中无法使用。请按原样阅读,不要把它当作实验之外的实际选项

一个用例可能是将它用作听众的字典,我有一个同事这样做了。这非常有用,因为任何听众都会直接以这种方式做事。再见听众.on.

但从更抽象的角度来看,WeakMap(弱点地图)对基本上任何东西的非物质化访问都特别强大,您不需要名称空间来隔离其成员,因为它已经被此结构的性质所暗示。我非常确信,通过替换笨拙的冗余对象键,您可以做一些主要的内存改进(尽管解构可以为您完成这项工作)。


在阅读下一步内容之前

我现在确实意识到我的强调并不是解决问题的最佳方式本杰明·格伦鲍姆指出(检查他的答案,如果它还没有超过我的答案:p),这个问题不可能用常规方法解决地图,因为它会泄漏,因此WeakMap(弱点地图)因为它们不保留引用,所以不会干扰垃圾收集。


这是我同事的实际代码(感谢用于共享)

此处为完整源代码,这是关于我上面提到的侦听器管理(您也可以查看规格)

var listenableMap=新的WeakMap();导出函数getListenable(对象){if(!listenableMap.has(object)){listenableMap.set(对象,{});}return listenableMap.get(object);}导出函数getListeners(对象,标识符){var listenable=getListenable(对象);listenable[identifier]=可侦听[identififier]|[];返回可侦听的[identifier];}上的导出函数(对象、标识符、侦听器){var listeners=getListeners(对象,标识符);listeners.push(listener);}导出函数removeListener(对象、标识符、侦听器){var listeners=getListeners(对象,标识符);var index=监听器.indexOf(监听器);if(索引!==-1){侦听器。拼接(索引,1);}}导出函数emit(对象、标识符…参数){var listeners=getListeners(对象,标识符);for(监听器的var监听器){listener.apply(对象,参数);}}
6
  • 我不太明白你会怎么用这个。当不再引用时,它将导致可观察项连同绑定到它的事件一起崩溃。我倾向于遇到的问题是,观察者不再被引用。我认为这里的解决方案只解决了一半的问题。我认为你不能用WeakMap解决观察者问题,因为它是不可迭代的。 评论 2016年6月6日14:55
  • 1
    双缓冲事件监听器在其他语言中可能速度很快,但在这种情况下,它只是简单的深奥和缓慢。那是我的三分钱。 评论 2017年9月5日23:54
  • @axelduch,哇,这个听众处理的神话一直在Javascript社区兜售,获得了40张赞成票!了解原因这个答案完全错误,请参阅下的注释stackoverflow.com/a/156618/632951 评论 2017年9月18日18:42
  • 1
    @Pacerier更新了答案,感谢反馈 评论 2017年9月18日18:50
  • 1
    @axelduch,是的,那里也有裁判。 评论 2017年9月18日18:55
27

WeakMap(弱点地图)适用于封装和信息隐藏

WeakMap(弱点地图)仅适用于ES6及以上版本。A类WeakMap(弱点地图)是键和值对的集合,其中键必须是对象。在下面的示例中,我们构建了一个WeakMap(弱点地图)有两个项目:

var-map=新的WeakMap();var pavloHero={first:“Pavlo”,last:“Hero”};var gabrielFranco={first:“Gabriel”,last:“Franco”};map.set(pavloHero,“这是英雄”);map.set(gabrielFranco,“这是Franco”);console.log(map.get(pavloHero))//这是英雄

我们使用了设置()方法来定义对象和另一项(在我们的示例中是字符串)之间的关联。我们使用了获取()方法检索与对象关联的项。有趣的方面WeakMap(弱点地图)s是一个事实,它包含对映射中键的弱引用。弱引用意味着如果对象被销毁,垃圾收集器将从WeakMap(弱点地图)从而释放内存。

var剧院座位=(function(){var priv=新的WeakMap();var=函数(实例){return priv.get(实例);};return(函数(){函数TheatreSeatsConstructor(){var privateMembers={座位:[]};priv.set(this,privateMembers);this.maxSize=10;}TheatreSeatsConstructor.prototype.placePerson=功能(人){_(本).座位.推(人);};TheatreSeatsConstructor.prototype.countOccupiedSeats=功能(){返回_(本).座位.长度;};TheatreSeatsConstructor.prototype.isSaldOut=函数(){return _(this).seats.length>=this.maxSize;};TheatreSeatsConstructor.prototype.countFreeSeats=函数(){return this.maxSize-_(this).seats.length;};return TheatreSeatsConstructor;}());})()
2
  • 5
    关于“weakmap在封装和信息隐藏方面表现良好”。仅仅因为你可以,并不意味着你应该。即使在weakmap发明之前,Javascript也有默认的封装和信息隐藏方法。到目前为止从字面上看,有6种方法可以做到这一点使用weakmap进行封装是一种丑陋的手段。 评论 2017年9月18日19:41
  • 解释得很清楚 评论 2022年8月9日2:01
19

𝗠𝗲𝘁𝗮𝗱𝗮𝘁𝗮

弱映射可以用于存储有关DOM元素的元数据,而不会干扰垃圾收集或让同事对您的代码感到愤怒。例如,您可以使用它们对网页中的所有元素进行数字索引。

𝗪𝗶𝘁𝗵𝗼𝘂𝘁 𝗪𝗲𝗮𝗸𝗠𝗮𝗽𝘀 𝗼𝗿 𝗪𝗲𝗮𝗸𝗦𝗲𝘁𝘀:

var elements=document.getElementsByTagName('*'),i=-1,len=元件长度;while(++i!==长度){//写得这么糟糕的生产代码让我想哭:元素[i].lookupindex=i;元素[i].elementref=[];elements[i].elementref.push(elements[(i*i)%len]);}//然后,您可以访问查找索引的//对于那些刚接触javascirpt的人,我希望下面的评论能够帮助解释//三元运算符(?:)如何像内联if语句一样工作文档.write(document.body.lookupindex+“<br/>”+((document.body.elementref.indexOf(document.currentScript)!==-1)? // if(document.body.elementref.indexOf(document.currentScript)!==-1){“正确”://}其他{“假”)   // });

𝗨𝘀𝗶𝗻𝗴 𝗪𝗲𝗮𝗸𝗠𝗮𝗽𝘀 𝗮𝗻𝗱 𝗪𝗲𝗮𝗸𝗦𝗲𝘁𝘀:

var DOMref=新的WeakMap(),__DOMref_value=数组,__DOMref_lookupindex=0,__DOMref_otherelement=1,elements=document.getElementsByTagName('*'),i=-1,len=元素长度,cur;while(++i!==长度){//写得这么好的生产代码让我想😊:cur=DOMref.get(元素[i]);if(cur===未定义)DOMref.set(元素[i],cur=新__DOMref_value)cur[__DOMref_lookupindex]=i;cur[__DOMref_otherelement]=新的WeakSet();cur[__DOMref_otherelement].add(元素[(i*i)%len]);}//然后,您可以访问lookupindex的cur=DOMref.get(document.body)document.write(cur[__DOMref_lookupindex]+'<br/>'+(cur[__DOMref_otherelement].has(document.currentScript)? // if(cur[__DOMref_otherelement].has(document.currentScript)){“正确”://}其他{“假”)   // });

𝗧𝗵𝗲 𝗗𝗶𝗳𝗳𝗲𝗿𝗲𝗻𝗰𝗲

除了弱映射版本更长之外,这种差异看起来可以忽略不计,但是上面显示的两段代码之间存在着重大差异。在第一段代码中,没有弱映射,代码段存储DOM元素之间的所有引用。这可以防止对DOM元素进行垃圾收集。(i*i)%长度可能看起来很奇怪,没有人会使用,但请再想一想:大量生产代码都有DOM引用,这些引用在文档中到处都是。现在,对于第二段代码,由于对元素的所有引用都很弱,当您删除节点时,浏览器能够确定该节点未被使用(代码无法访问),从而将其从内存中删除。您应该关注内存使用情况和内存锚定(比如内存中保存未使用元素的第一段代码)的原因是,内存使用量越大,意味着浏览器的GC尝试越多(试图释放内存以避免浏览器崩溃),意味着浏览体验越慢,有时甚至会导致浏览器崩溃。

至于这些的聚乙烯填充物,我会推荐我自己的图书馆(在这里找到@github). 这是一个非常轻量级的库,它将简单地对其进行聚合填充,而不会使用其他聚合填充中可能找到的任何全面复杂的框架。

~快乐编码!

5
  • 1
    谢谢你的明确解释。一个例子比任何一句话都更有价值。
    – 新人
    评论 2017年8月8日4:09
  • @lolzery,Re“这可以防止对DOM元素进行垃圾收集“,你只需要设置元素至零你完成了:这将是GCed。&回复“在文档中反弹的DOM引用“,根本不重要:一旦主链接元素如果没有,所有循环引用都将被GCed。如果元素保留了对它不需要的元素的引用,那么修复代码并将ref设置为null当你用完它。它会被GCed。不需要弱点. 评论 2017年9月18日20:45
  • 4
    @Pacerier感谢您的热情反馈,无论设置如何元素遗嘱无效允许浏览器GC第一个片段中的元素。这是因为您在元素上设置了自定义属性,然后仍然可以获得这些元素,并且仍然可以访问它们的自定义属性,从而防止它们中的任何一个被GC’ed。将其想象为一个金属环链。只要你有权访问链中的至少一个链接,你就可以抓住链中的链接,从而防止整个项目链落入深渊。 评论 2017年9月19日1:21
  • 那个三元数在我看来很冗长。如果你需要布尔值的字符串表示,你可以这样做`${true}`//“true” 评论 2020年8月4日16:22
  • 2
    @LukaszMatysik这是一个更短、更跨浏览器的版本:“”+真。我不会对代码进行这种更改,因为代码的目标是让人可读,而不是最大限度地节省空间。并不是每个人都像你和我一样了解JS。有些初学者只是想开始学习这门语言。当我们炫耀自己对JS的高级知识时,这对他们毫无帮助。 评论 2020年8月5日12:28
16

我使用WeakMap(弱点地图)用于缓存将不可变对象作为参数的函数的无忧记忆。

记忆是一种奇特的说法,即“计算值后,缓存它,这样就不必再计算它”。

下面是一个示例:

//从此处使用immutable.jshttps://facebook.github.io/immutable-js/const-memo=新WeakMap();让myObj=不变。地图({a:5,b:6});函数someLongComputeFunction(someImmutableObj){//如果我们保存了值,则返回它if(memor.has(someImmutableObj)){console.log('已使用备忘录!');return memo.get(someImmutableObj);}//否则计算、设置并返回const computedValue=someImmutableObj.get('a')+someImMUtableOb2.get(‘b');memo.set(someImmutableObj,computedValue);console.log(“计算值”);return computedValue;}someLongComputeFunction(myObj);someLongComputeFunction(myObj);someLongComputeFunction(myObj);//重新指派myObj=不变。地图({a:7,b:8});someLongComputeFunction(myObj);
<script src=“https://cdnjs.cloudflare.com/ajax/libs/inmunitable/3.8.1/immutable.min.js“></script>

需要注意的几点:

  • 当您修改Immutable.js对象时,它们会返回新的对象(带有新指针),因此在WeakMap中使用它们作为键可以保证相同的计算值。
  • WeakMap非常适合备忘录,因为一旦对象(用作键)被垃圾收集,WeakMap上的计算值也会被垃圾收集。
1
  • 1
    这是weakmap的有效用法,只要记忆缓存是记忆敏感的,在obj/函数的整个生命周期中不持久。如果“memoization cache”意味着在对象/函数的整个生命周期中都是持久的,那么weakmap是错误的选择:使用6种默认javascript封装技术中的任何一种而不是。 评论 2017年9月18日20:55
13

我有一个简单的WeakMaps基于功能的用例/示例。

管理用户集合

我从一个用户其属性包含全名,用户名,年龄,性别和一个名为打印它打印其他属性的可读摘要。

/**具有通用属性的基本用户对象。*/功能用户(用户名、全名、年龄、性别){this.username=用户名;this.fullname=全名;this.age=年龄;this.gender=性别;this.print=()=>console.log(`${this.fullname}是${age}岁的${gender}`);}

然后我添加了一个名为用户保存由键控的多个用户的集合用户名.

/**用户集合,由用户名键入。*/var users=新映射();

添加Collection还需要helper函数来添加、获取、删除User,甚至需要一个函数来打印所有用户,以确保完整性。

/**创建用户对象并将其添加到用户集合。*/var addUser=(用户名、全名、年龄、性别)=>{让an_user=新用户(用户名、全名、年龄、性别);users.set(用户名,an_user);}/**返回与集合中给定用户名关联的用户对象。*/var getUser=(用户名)=>{return users.get(用户名);}/**删除与集合中给定用户名关联的用户对象。*/var deleteUser=(用户名)=>{users.delete(用户名);}/**打印集合中所有用户对象的摘要。*/var printUsers=()=>{users.forEach((用户)=>{user.print();});}

假设上面的所有代码都在运行节点JS,只有用户Map在整个流程中引用用户对象。没有其他对单个用户对象的引用。

运行此代码是一个交互式NodeJS shell,就像示例一样,我添加了四个用户并打印它们:添加和打印用户

在不修改现有代码的情况下向用户添加更多信息

现在假设需要一个新功能,其中需要跟踪每个用户的社交媒体平台(SMP)链接以及用户对象。

这里的关键还在于,必须在对现有代码进行最小干预的情况下实现此功能。

使用WeakMaps可以通过以下方式实现这一点。

我为Twitter、Facebook和LinkedIn添加了三个单独的WeakMaps。

/*社交媒体平台(SMP)的WeakMaps。可以替换为一个可以增长的地图基于不同的SMP名称。无论如何。。。*/var sm_platform_twitter=新的WeakMap();var sm_platform_facebook=新的WeakMap();var sm_platform_linkedin=新的WeakMap();

辅助功能,获取SMPWeakMap仅添加以返回与给定SMP名称关联的WeakMap。

/**返回给定SMP的WeakMap。*/var getSMPWeakMap=(sm_platform)=>{if(sm_platform==“推特”){返回sm_platform_twitter;}else if(sm_platform==“Facebook”){返回sm_platform_facebook;}else if(sm_platform==“LinkedIn”){返回sm_platform_linkedin;}返回未定义;}

将用户SMP链接添加到给定SMP WeakMap的函数。

/**添加与给定用户关联的SMP链接。用户必须已添加到集合中。*/var addUserSocialMediaLink=(用户名,sm_platform,sm_link)=>{let user=getUser(用户名);让sm_platform_weakmap=getSMPWeakMap(sm_platform);if(用户&&sm_platform_weakmap){sm_platform_weakmap.set(用户,sm_link);}}

只打印给定SMP上的用户的功能。

/**仅打印给定SMP上的用户的全名和相应的SMP链接。*/var printSMPUsers=(sm_platform)=>{让sm_platform_weakmap=getSMPWeakMap(sm_platform);console.log(`${sm_platform}:`的用户)users.forEach((用户)=>{if(sm_platform_weakmap.has(用户)){console.log(`\t${user.fullname}:${sm_platform_weakmap.get(user)})}});}

现在您可以为用户添加SMP链接,也可以让每个用户在多个SMP上都有一个链接。

…继续前面的示例,我向用户添加SMP链接,为用户Bill和Sarah添加多个链接,然后分别打印每个SMP的链接:向用户添加SMP链接并显示它们

现在假设用户已从用户通过调用映射删除用户。这将删除对用户对象的唯一引用。这反过来也将从任何/所有SMP WeakMaps中清除SMP链接(通过垃圾收集),因为如果没有用户对象,则无法访问其任何SMP链接。

…继续示例,我删除用户比尔然后打印出与他关联的SMP的链接:

从Map中删除用户Bill也会删除SMP链接

不需要任何附加代码来单独删除SMP链接,并且在该功能未被修改之前的现有代码也不需要修改。

如果有任何其他方式可以添加此功能,无论是否使用WeakMaps,请随时发表评论。

2
  • 2
    _____很好______ 评论 2020年5月6日23:07
  • 2
    谢谢你,这是我读过的第一个非常清晰的例子,解释了什么时候这些工具会派上用场。
    – 开关386
    评论 2021年10月30日1:35
10

威克迈:请记住,weakMap只与内存分配和垃圾收集有关,只与对象类型的键有关在javascript中,当u将值存储在键值对数组、映射、集合等中时……一个分配给所有键值对的内存,即使删除或将该键设置为null,该内存也不会空闲。考虑到这是一个强映射键,如下所示

让yusuf={name:“yusuf”};让map=新map();map.set(yusuf,“xyz”)//这里“yusuf”是键,“xyz”是值yusuf=空;//覆盖引用//yusuf先前引用的对象存储在数组中//因此它不会被垃圾收集//我们可以使用map.keys()获得它

但这不是weakMap的情况,这里的内存将是空闲的

让yusuf={name:“yusuf”};let map=新的WeakMap();map.set(优素福,“…”);yusuf=空;//覆盖引用//尤素夫已从记忆中消失!

用例:您将在javascript中使用它,以便以更高效的方式管理内存

如果我们正在处理一个“属于”另一个代码的对象,甚至可能是第三方库,并且希望存储一些与之相关的数据,那么该对象应该只在对象存在时存在,那么WeakMap正是我们所需要的。

我们将数据放在WeakMap中,使用对象作为键,当对象被垃圾收集时,数据也会自动消失。

weakMap.set(yusuf,“秘密文件”);//如果优素福死了,秘密文件将自动销毁

我引用了这篇伟大的文章:https://javascript.info/weakmap-weakset

0
const aNormalMap=新贴图();函数pushToMap(){for(设i=0;i<10;i++){aNormalMap.set({},i);}}const aWeakMap=新WeakMap();函数pushToWeakMap(){for(设i=0;i<10;i++){aWeakMap.set({},i);}}函数main(){pushToMap();/***调用pushToMap后*/pushToWeakMap();/***调用pushToWeakMap后*/}

呼叫后推送到地图,有不可能去拿aNormalMap(法线贴图)通过按键,因为按键(即。{})对于pushToWeakMap是本地的,在pushToMap存在后,键(即。{})因为无法实现,让我重申一下这一点

钥匙(即。{})函数存在后不可访问,应该对其进行垃圾收集

然而,这样一个“不可访问”的键将存在于内存中永远,因为即使密钥本身不可访问,我们仍然可以通过枚举aNormalMap(法线贴图).因此“不可访问”不能是GC

另一方面,“不可访问”键将不是驻留在内存中,即使它由a薄弱环节地图,因为它是

-1

我认为这对检查应用程序套接字中的连接收入非常有用。另一种情况下,“弱集合”很有用:https://javascript.info/task/recipients-read

你的答案

单击“发布您的答案”,表示您同意我们的服务条款并确认您已阅读我们的隐私政策.

不是你想要的答案吗?浏览标记的其他问题问你自己的问题.