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 可以自动解决这个问题。

javascript 复制代码
// 创建一个 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 可以用于模拟对象的私有属性,这些私有属性会随着对象的销毁而自动消失。

javascript 复制代码
// 使用 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 是很好的选择。

javascript 复制代码
// 使用 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。

关注一下呗

相关推荐
Yvonne爱编码20 小时前
AJAX入门-AJAX 概念和 axios 使用
前端·javascript·ajax·html·js
小天呐3 天前
qiankun 微前端接入实战
前端·js·微前端
脑子慢且灵9 天前
【Web前端】JS+DOM来实现乌龟追兔子小游戏
java·开发语言·前端·js·dom
上单带刀不带妹11 天前
Node.js 的流(Stream)是什么?有哪些类型?
node.js·stream·js
丁同亚的博客12 天前
echarts大屏项目指南
echarts·可视化·js·web前端·大屏
乔公子搬砖16 天前
小程序开发提效:npm支持、Vant Weapp组件库与API Promise化(八)
前端·javascript·微信小程序·js·promise·vagrant·事件绑定
110546540120 天前
37、需求预测与库存优化 (快消品) - /供应链管理组件/fmcg-inventory-optimization
前端·信息可视化·数据分析·js
我命由我123451 个月前
软件开发 - 避免过多的 if-else 语句(使用策略模式、使用映射表、使用枚举、使用函数式编程)
java·开发语言·javascript·设计模式·java-ee·策略模式·js
第七种黄昏1 个月前
大事件项目拆解:登录访问拦截实现详解
前端框架·vue·js