WeakMap 是 JavaScript 中一种非常有用的数据结构,它通过弱引用机制来帮助管理内存,防止内存泄漏。简单来说,当你用一个对象作为 WeakMap 的键时,WeakMap 不会阻止这个对象被垃圾回收器回收。一旦这个对象在其他地方没有被引用了,它以及它在 WeakMap 中对应的值就会被自动清理掉。
下面是一个对比 WeakMap 和 Map 主要特性的表格,帮助你快速了解它们的区别:
特性WeakMapMap键类型只接受对象作为键任何类型(对象、原始值)均可作为键引用机制对键是弱引用,不阻止垃圾回收对键是强引用,防止键被垃圾回收可遍历性不可遍历(无 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。
关注一下呗
来源:程序园用户自行投稿发布,如果侵权,请联系站长删除
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作! |