WeakMap 应用场景与示例
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 的注意点
- 键必须是对象:WeakMap 的键只能是对象(包括数组、函数等),不能是原始值(如字符串、数字、Symbol、
null、undefined)。尝试使用原始值作为键会抛出TypeError。 - 不可遍历:由于弱引用的特性,WeakMap 没有
size属性,也不能遍历其键或值(例如,没有keys(),values(),entries()方法,也不能使用forEach)。你只能通过get(key),set(key, value),has(key), 和delete(key)来操作单个键值对。 - 不支持
clear()方法:WeakMap 没有清空所有键值对的方法。 - 垃圾回收时机不确定:虽然 WeakMap 中的键值对会在键对象被垃圾回收后自动消失,但垃圾回收的具体发生时机是由 JavaScript 引擎决定的,你无法立即感知到。
何时选择 WeakMap vs. Map
选择 WeakMap 的情况:
你需要将数据(元数据、缓存、私有属性)与对象关联起来,并且希望这些数据的生命周期跟随该对象,自动管理,避免内存泄漏。你也不需要遍历这些数据或知道其数量。选择 Map 的情况:
你的键可以是任何类型(包括原始值)。你需要遍历键值对、需要知道数量(size)、或者需要长期稳定地维护一组键值对集合,而不希望键被自动垃圾回收。
希望这些解释和示例能帮助你更好地理解和使用 WeakMap。
关注一下呗
WeakMap 应用场景与示例的更多相关文章
- Java BitSet使用场景和示例
一.什么是BitSet? 注:以下内容来自JDK API: BitSet类实现了一个按需增长的位向量.位Set的每一个组件都有一个boolean值.用非负的整数将BitSet的位编入索引.可以对每个编 ...
- Firefly的角色跳转场景简单示例
源地址:http://bbs.9miao.com/thread-45790-1-2.html 本例演示的是模拟游戏服务端,让角色在场景1中跳转到场景2中.在实际游戏中,client将要跳转的角色id和 ...
- ibeacon的使用和应用场景简单示例
目的,用ibeacon实现签到功能,不需要太严谨,只是试水. 拿到ibeacon的第一感觉是,这东西能用嘛,2-3年的电池,后面商家说是用个3M双面胶找个地方一贴就行,感觉不太靠谱,嘿嘿,在网上找了一 ...
- 引用、浅拷贝及深拷贝 到 Map、Set(含对象assign、freeze方法、WeakMap、WeakSet及数组map、reduce等等方法)
从引用聊到深浅拷贝,从深拷贝过渡到ES6新数据结构Map及Set,再到另一个map即Array.map()和与其类似的Array.flatMap(),中间会有其他相关话题,例如Object.freez ...
- ActiveMQ笔记(1):编译、安装、示例代码
一.编译 虽然ActiveMQ提供了发布版本,但是建议同学们自己下载源代码编译,以后万一有坑,还可以尝试自己改改源码. 1.1 https://github.com/apache/activemq/r ...
- ML.NET 示例:推荐之场感知分解机
写在前面 准备近期将微软的machinelearning-samples翻译成中文,水平有限,如有错漏,请大家多多指正. 如果有朋友对此感兴趣,可以加入我:https://github.com/fei ...
- ML.NET 示例:推荐之矩阵分解
写在前面 准备近期将微软的machinelearning-samples翻译成中文,水平有限,如有错漏,请大家多多指正. 如果有朋友对此感兴趣,可以加入我:https://github.com/fei ...
- es6 Map,Set 和 WeakMap,WeakSet
这些是新加的集合类型,提供了更加方便的获取属性值的方法,不用像以前一样用hasOwnProperty来检查某个属性是属于原型链上的呢还是当前对象的.同时,在进行属性值添加与获取时有专门的get,set ...
- 场景7:带有Linux网桥的提供商网络
此场景描述了使用带有Linux网桥的ML2插件的OpenStack网络服务的供应商网络实现. 供应商网络通常以灵活性为代价提供简单性.性能和可靠性.与其他场景不同,只有管理员可以管理提供者网络,因为它 ...
- 如何根据不同业务场景调节 HPA 扩缩容灵敏度
背景 在 K8s 1.18 之前,HPA 扩容是无法调整灵敏度的: 对于缩容,由 kube-controller-manager 的 --horizontal-pod-autoscaler-downs ...
随机推荐
- 分享一个 Cursor mdc 生成器,基于 Gemini 2.5,很实用!
大家好,我是 Immerse,一名独立开发者.内容创作者. 关注公众号:#沉浸式趣谈,获取最新文章(更多内容只在公众号更新) 个人网站:https://yaolifeng.com 也同步更新. 转载请 ...
- node安装与使用
nvm for mac/linx 安装 使用yum安装node,最新只能安装到0.12.X 版本,而自定义安装二进制的又有点麻烦,所以用nvm安装 (如果命令下载不下来 可以手动下载到服务器上执行). ...
- java实现聊天,服务端与客户端代码(UDP)-狂神改
首先是文件结构: 最后run的是下面两个 代码用的狂神的,不过他写的有点小bug,比如传信息会出现一堆空格(recieve data那里长度不应该用data.lenth()而应该用packet.get ...
- AI 为何能查天气、订机票?揭秘大模型背后的“神秘工具箱”
你有没有想过,为什么 AI 能回答"今天上海天气怎么样?"这种实时问题,甚至帮你预订机票?明明它的训练数据截止到去年,怎么会对现在的事情了如指掌? 答案就藏在一个核心技术里--工具 ...
- SciTech-Science: 纯色滤(分)光塑料片: 将光分解为BGR三原纯色(彩色CCD传感器原理) + “502熏显法”采集“指纹”与Glue胶水: 普通胶水是“胶”与“水”混合物因此不会黏上瓶子
彩色滤(分)光塑料片: 将光分解为BGR三原纯色 彩色CCD传感器原理 透过 一张 彩色滤(分)光塑料片 可以分解出 光源的"与滤光片同颜色"的成份: 例如 "B(蓝色) ...
- 本文将告诉你学习Java的一些步骤 --九五小庞
- Win10专业版更新驱动出现闪屏的问题
有一位深度技术的用户,在电脑上安装的是win10系统,安装完后并没有出现什么问题.但是,之后使用驱动精灵更新了驱动,重启之后电脑就出现闪屏了,系统也进不去的问题.该如何解决呢?接下来,深度官网小编就来 ...
- Unity 编辑器UI 杂记
用 rootVisualElement 方法绘制按钮和用 GUILayout.Button 绘制按钮混用的案例 using System.Collections; using System.Colle ...
- 轮廓线 dp
轮廓线 dp 是一种和插头 dp 基本相同的东西,所以先看一下轮廓线 dp. Tiling Dominoes 与状压 dp 不同的是,轮廓线 dp 是通过逐格转移来进行 dp 的.我们用三维 \(f_ ...
- if-else问题
为什么会有这个问题,最近碰到很多逻辑判断,此时需要用到if-else 以前就听说判断if语句,一定要注意else问题,以前不以为意.现在碰到好多异常现象,每个if语句后面需要重新分析其他分支情况