WeakMap 是 JavaScript 中一种非常有用的数据结构,它通过弱引用机制来帮助管理内存,防止内存泄漏。简单来说,当你用一个对象作为 WeakMap 的键时,WeakMap 不会阻止这个对象被垃圾回收器回收。一旦这个对象在其他地方没有被引用了,它以及它在 WeakMap 中对应的值就会被自动清理掉。

下面是一个对比 WeakMap 和 Map 主要特性的表格,帮助你快速了解它们的区别:

特性 WeakMap Map
键类型 只接受对象作为键 任何类型(对象、原始值)均可作为键
引用机制 对键是弱引用,不阻止垃圾回收 对键是强引用,防止键被垃圾回收
可遍历性 不可遍历(无 keys(), values(), entries() 方法,无 size 属性) 可遍历,有 size 属性
内存管理 自动清理,不易内存泄漏 需手动管理,可能内存泄漏
主要使用场景 需要与对象生命周期关联的元数据、缓存或私有数据存储 需要频繁遍历、查询或维护固定键值对集合的场景

WeakMap 主要应用场景与 Demo

WeakMap 的设计特点使得它特别适合用于那些需要将数据与对象关联,但又不想影响这些对象生命周期(即垃圾回收)的场景。

1. 为 DOM 元素存储元数据

当需要为 DOM 元素添加一些附加数据(如状态、事件处理器等)时,如果直接存储在普通对象或 Map 中,即使 DOM 元素从页面上移除,由于 Map 还引用着它,它也不会被垃圾回收,导致内存泄漏。WeakMap 可以自动解决这个问题。

// 创建一个 WeakMap 来存储每个 div 元素的点击次数
const domElementMetadata = new WeakMap(); // 获取一个 div 元素
const myDiv = document.createElement('div');
document.body.appendChild(myDiv); // 为该 div 元素初始化元数据
domElementMetadata.set(myDiv, { clickCount: 0 }); // 给 div 添加点击事件,更新元数据
myDiv.addEventListener('click', function() {
const metadata = domElementMetadata.get(myDiv);
metadata.clickCount++;
console.log(`该 div 已被点击 ${metadata.clickCount} 次`);
}); // 假设未来某个时刻,myDiv 从 DOM 中被移除,并且没有其他变量引用它
// myDiv.remove(); // 从 DOM 移除
// myDiv = null; // 移除引用 // 此后,垃圾回收器可以自动回收 myDiv 对象,domElementMetadata 中对应的键值对也会被自动清除

2. 存储对象的私有数据

在 JavaScript 中,实现真正的私有成员比较麻烦。WeakMap 可以用于模拟对象的私有属性,这些私有属性会随着对象的销毁而自动消失。

// 使用 WeakMap 来模拟私有属性
const _privateData = new WeakMap(); class MyClass {
constructor(publicValue) {
// 将私有数据存储在 WeakMap 中,以当前实例 this 为键
_privateData.set(this, {
secret: `This is a secret for ${publicValue}`,
internalCounter: 0
});
this.publicValue = publicValue;
} getSecret() {
// 只有通过实例方法才能访问到对应的私有数据
const data = _privateData.get(this);
data.internalCounter++;
return data.secret;
} getCallCount() {
return _privateData.get(this).internalCounter;
}
} // 使用类
const instance1 = new MyClass('Instance One');
console.log(instance1.getSecret()); // "This is a secret for Instance One"
console.log(instance1.getCallCount()); // 1 const instance2 = new MyClass('Instance Two');
console.log(instance2.getSecret()); // "This is a secret for Instance Two"
console.log(instance2.getCallCount()); // 1 // 当 instance1 被置为 null,它就可以被垃圾回收,_privateData 中对应的私有数据也会被自动清理
// instance1 = null;

3. 缓存计算结果

当需要根据特定对象缓存耗时的计算结果,并且希望缓存的生命周期与该对象保持一致时,WeakMap 是很好的选择。

// 使用 WeakMap 缓存与对象相关的昂贵计算结果
const computationCache = new WeakMap(); function intensiveComputation(obj) {
// 如果缓存中存在该对象的结果,则直接返回
if (computationCache.has(obj)) {
console.log('从缓存中获取结果');
return computationCache.get(obj);
} // 模拟一个耗时的计算过程
console.log('执行计算...');
const result = JSON.stringify(obj); // 假设这是一个昂贵的操作
// 将计算结果缓存到 WeakMap 中,以输入对象为键
computationCache.set(obj, result); return result;
} // 使用缓存函数
const inputObj1 = { data: "test1" };
const result1 = intensiveComputation(inputObj1); // 输出 "执行计算..."
const result1Cached = intensiveComputation(inputObj1); // 输出 "从缓存中获取结果" const inputObj2 = { data: "test2" };
const result2 = intensiveComputation(inputObj2); // 输出 "执行计算..." // 当 inputObj1 不再被需要,并被置为 null 时
// inputObj1 = null;
// 垃圾回收后,computationCache 中对应的缓存项也会被自动清除

️ 使用 WeakMap 的注意点

  1. 键必须是对象:WeakMap 的键只能是对象(包括数组、函数等),不能是原始值(如字符串、数字、Symbol、nullundefined)。尝试使用原始值作为键会抛出 TypeError
  2. 不可遍历:由于弱引用的特性,WeakMap 没有 size 属性,也不能遍历其键或值(例如,没有 keys(), values(), entries() 方法,也不能使用 forEach)。你只能通过 get(key), set(key, value), has(key), 和 delete(key) 来操作单个键值对。
  3. 不支持 clear() 方法:WeakMap 没有清空所有键值对的方法。
  4. 垃圾回收时机不确定:虽然 WeakMap 中的键值对会在键对象被垃圾回收后自动消失,但垃圾回收的具体发生时机是由 JavaScript 引擎决定的,你无法立即感知到。

何时选择 WeakMap vs. Map

  • 选择 WeakMap 的情况:

    你需要将数据(元数据、缓存、私有属性)与对象关联起来,并且希望这些数据的生命周期跟随该对象自动管理避免内存泄漏。你也不需要遍历这些数据或知道其数量。

  • 选择 Map 的情况:

    你的键可以是任何类型(包括原始值)。你需要遍历键值对、需要知道数量size)、或者需要长期稳定地维护一组键值对集合,而不希望键被自动垃圾回收。


希望这些解释和示例能帮助你更好地理解和使用 WeakMap。

关注一下呗

WeakMap 应用场景与示例的更多相关文章

  1. Java BitSet使用场景和示例

    一.什么是BitSet? 注:以下内容来自JDK API: BitSet类实现了一个按需增长的位向量.位Set的每一个组件都有一个boolean值.用非负的整数将BitSet的位编入索引.可以对每个编 ...

  2. Firefly的角色跳转场景简单示例

    源地址:http://bbs.9miao.com/thread-45790-1-2.html 本例演示的是模拟游戏服务端,让角色在场景1中跳转到场景2中.在实际游戏中,client将要跳转的角色id和 ...

  3. ibeacon的使用和应用场景简单示例

    目的,用ibeacon实现签到功能,不需要太严谨,只是试水. 拿到ibeacon的第一感觉是,这东西能用嘛,2-3年的电池,后面商家说是用个3M双面胶找个地方一贴就行,感觉不太靠谱,嘿嘿,在网上找了一 ...

  4. 引用、浅拷贝及深拷贝 到 Map、Set(含对象assign、freeze方法、WeakMap、WeakSet及数组map、reduce等等方法)

    从引用聊到深浅拷贝,从深拷贝过渡到ES6新数据结构Map及Set,再到另一个map即Array.map()和与其类似的Array.flatMap(),中间会有其他相关话题,例如Object.freez ...

  5. ActiveMQ笔记(1):编译、安装、示例代码

    一.编译 虽然ActiveMQ提供了发布版本,但是建议同学们自己下载源代码编译,以后万一有坑,还可以尝试自己改改源码. 1.1 https://github.com/apache/activemq/r ...

  6. ML.NET 示例:推荐之场感知分解机

    写在前面 准备近期将微软的machinelearning-samples翻译成中文,水平有限,如有错漏,请大家多多指正. 如果有朋友对此感兴趣,可以加入我:https://github.com/fei ...

  7. ML.NET 示例:推荐之矩阵分解

    写在前面 准备近期将微软的machinelearning-samples翻译成中文,水平有限,如有错漏,请大家多多指正. 如果有朋友对此感兴趣,可以加入我:https://github.com/fei ...

  8. es6 Map,Set 和 WeakMap,WeakSet

    这些是新加的集合类型,提供了更加方便的获取属性值的方法,不用像以前一样用hasOwnProperty来检查某个属性是属于原型链上的呢还是当前对象的.同时,在进行属性值添加与获取时有专门的get,set ...

  9. 场景7:带有Linux网桥的提供商网络

    此场景描述了使用带有Linux网桥的ML2插件的OpenStack网络服务的供应商网络实现. 供应商网络通常以灵活性为代价提供简单性.性能和可靠性.与其他场景不同,只有管理员可以管理提供者网络,因为它 ...

  10. 如何根据不同业务场景调节 HPA 扩缩容灵敏度

    背景 在 K8s 1.18 之前,HPA 扩容是无法调整灵敏度的: 对于缩容,由 kube-controller-manager 的 --horizontal-pod-autoscaler-downs ...

随机推荐

  1. 分享一个 Cursor mdc 生成器,基于 Gemini 2.5,很实用!

    大家好,我是 Immerse,一名独立开发者.内容创作者. 关注公众号:#沉浸式趣谈,获取最新文章(更多内容只在公众号更新) 个人网站:https://yaolifeng.com 也同步更新. 转载请 ...

  2. node安装与使用

    nvm for mac/linx 安装 使用yum安装node,最新只能安装到0.12.X 版本,而自定义安装二进制的又有点麻烦,所以用nvm安装 (如果命令下载不下来 可以手动下载到服务器上执行). ...

  3. java实现聊天,服务端与客户端代码(UDP)-狂神改

    首先是文件结构: 最后run的是下面两个 代码用的狂神的,不过他写的有点小bug,比如传信息会出现一堆空格(recieve data那里长度不应该用data.lenth()而应该用packet.get ...

  4. AI 为何能查天气、订机票?揭秘大模型背后的“神秘工具箱”

    你有没有想过,为什么 AI 能回答"今天上海天气怎么样?"这种实时问题,甚至帮你预订机票?明明它的训练数据截止到去年,怎么会对现在的事情了如指掌? 答案就藏在一个核心技术里--工具 ...

  5. SciTech-Science: 纯色滤(分)光塑料片: 将光分解为BGR三原纯色(彩色CCD传感器原理) + “502熏显法”采集“指纹”与Glue胶水: 普通胶水是“胶”与“水”混合物因此不会黏上瓶子

    彩色滤(分)光塑料片: 将光分解为BGR三原纯色 彩色CCD传感器原理 透过 一张 彩色滤(分)光塑料片 可以分解出 光源的"与滤光片同颜色"的成份: 例如 "B(蓝色) ...

  6. 本文将告诉你学习Java的一些步骤 --九五小庞

  7. Win10专业版更新驱动出现闪屏的问题

    有一位深度技术的用户,在电脑上安装的是win10系统,安装完后并没有出现什么问题.但是,之后使用驱动精灵更新了驱动,重启之后电脑就出现闪屏了,系统也进不去的问题.该如何解决呢?接下来,深度官网小编就来 ...

  8. Unity 编辑器UI 杂记

    用 rootVisualElement 方法绘制按钮和用 GUILayout.Button 绘制按钮混用的案例 using System.Collections; using System.Colle ...

  9. 轮廓线 dp

    轮廓线 dp 是一种和插头 dp 基本相同的东西,所以先看一下轮廓线 dp. Tiling Dominoes 与状压 dp 不同的是,轮廓线 dp 是通过逐格转移来进行 dp 的.我们用三维 \(f_ ...

  10. if-else问题

    为什么会有这个问题,最近碰到很多逻辑判断,此时需要用到if-else 以前就听说判断if语句,一定要注意else问题,以前不以为意.现在碰到好多异常现象,每个if语句后面需要重新分析其他分支情况