WeakMap 在 JavaScript 中的应用场景

WeakMap 在 JavaScript 中的经典应用主要集中在 私有数据存储DOM 元素关联数据缓存管理 等场景。以下是几个经典示例:


1.私有属性存储(模拟私有成员)

在 ES6 之前,JavaScript 没有真正的私有属性(#privateField),使用 WeakMap 可以模拟类的私有成员,避免被外部访问。

示例:为类实例存储私有数据

const privateData = new WeakMap();

class Person {
  constructor(name, age) {
    // 将私有数据存储在 WeakMap 中,以实例为键
    privateData.set(this, { name, age });
  }

  getName() {
    return privateData.get(this).name;
  }

  getAge() {
    return privateData.get(this).age;
  }
}

const person = new Person("Alice", 30);
console.log(person.getName()); // "Alice"
console.log(person.name); // undefined(无法直接访问私有属性)

优势

  • 数据真正私有,外部无法通过 person.name 访问。
  • person 实例被垃圾回收时,WeakMap 中的关联数据自动清除。

2.DOM 元素关联额外数据

当需要为 DOM 元素存储临时状态或元数据时,WeakMap 可以避免直接修改 DOM 对象属性。

示例:跟踪按钮点击次数

const buttonClicks = new WeakMap();
const button = document.querySelector("#myButton");

// 初始化点击次数
buttonClicks.set(button, { count: 0 });

button.addEventListener("click", () => {
  const data = buttonClicks.get(button);
  data.count++;
  console.log(`Clicked ${data.count} times`);
});

优势

  • 不污染 DOM 元素(如 button._clickCount 这样直接赋值可能引发冲突)。
  • 如果按钮从 DOM 中移除,关联数据会自动被垃圾回收。

3.缓存计算结果(Memoization)

WeakMap 适合缓存与对象关联的计算结果,当对象销毁时缓存自动失效。

示例:缓存 DOM 元素的计算样式

const styleCache = new WeakMap();

function getComputedStyle(element) {
  if (styleCache.has(element)) {
    return styleCache.get(element); // 返回缓存结果
  }
  const computedStyle = window.getComputedStyle(element);
  styleCache.set(element, computedStyle); // 缓存结果
  return computedStyle;
}

const div = document.querySelector("div");
console.log(getComputedStyle(div)); // 首次计算并缓存
console.log(getComputedStyle(div)); // 直接返回缓存

优势

  • 避免重复计算,提升性能。
  • div 被移除时,缓存自动清除,节省内存。

4.管理对象监听器

为对象动态添加/移除事件监听器时,可以用 WeakMap 跟踪监听器,避免手动清理。

示例:为对象关联事件处理器

const eventListeners = new WeakMap();

function addListener(obj, handler) {
  const listeners = eventListeners.get(obj) || [];
  listeners.push(handler);
  eventListeners.set(obj, listeners);
}

function triggerEvent(obj) {
  const listeners = eventListeners.get(obj) || [];
  listeners.forEach((handler) => handler());
}

const user = { id: 1 };
addListener(user, () => console.log("Event triggered!"));
triggerEvent(user); // 输出 "Event triggered!"

优势

  • user 对象不再使用时,关联的监听器会自动被垃圾回收。

5.避免内存泄漏的缓存

普通 Map 缓存对象时会导致内存泄漏(即使对象已无用,但 Map 仍保留引用),而 WeakMap 不会。

示例:缓存图像对象

const imageCache = new WeakMap();

function loadImage(url) {
  const img = new Image();
  img.src = url;
  imageCache.set(img, { loaded: false });
  img.onload = () => {
    imageCache.set(img, { loaded: true });
  };
  return img;
}

const myImage = loadImage("example.jpg");
// 当 myImage 不再被引用时,imageCache 中的条目自动清除。

总结:WeakMap 的经典应用场景

场景 关键优势
私有属性存储 数据真正私有,随实例销毁自动清理
DOM 元素关联数据 不污染 DOM,自动垃圾回收
缓存计算结果 对象销毁时缓存自动失效
管理监听器 避免手动移除监听器,防止内存泄漏
临时元数据存储 安全地关联对象与临时数据

何时选择 WeakMap

  • 需要将数据与对象生命周期绑定
  • 不希望数据暴露或污染原始对象。
  • 键必须是对象(不能是原始值如字符串、数字)。

注意WeakMap 不可遍历(没有 keys()/values() 方法),因此仅适用于键对象已知的场景。