WeakMap / WeakSet 的「弱引用魔法」:实用的 JavaScript 内存优化工具

26 阅读3分钟

写在前面
如果你写过 React / React Native / Web 应用,一定遇到过:
👉 页面返回了,但内存没降
👉 组件卸载了,但监听器还在
👉 Map 缓存越用越大,却不敢清

这 90% 都不是 GC 的问题,而是:你用了“不该用 Map 的地方”。


一、一个每天都在写的“隐形内存泄漏”

我们先看一段极其常见的代码:

const cache = new Map();

function bind(obj, data) {
  cache.set(obj, data);
}

乍一看没问题,但问题在于:

let obj = { id: 1 };
bind(obj, { meta: 'xxx' });

obj = null; // 你以为内存会释放?

不会。

因为:

  • Map 对 key 是强引用
  • cache 还活着
  • GC 永远不可能回收 obj

👉 这就是大量前端 / RN 项目内存上涨的根源。


二、WeakMap 的“弱引用魔法”是什么?

一句话解释:

WeakMap 不会阻止 GC 回收它的 key

const wm = new WeakMap();
let obj = { id: 1 };

wm.set(obj, 'data');
obj = null; // ✅ GC 可回收

📌 数据是否存在,完全取决于 对象是否还活着


三、WeakMap / WeakSet 不是 Map / Set 的“简化版”

这是一个很多人会踩的认知误区。

能力MapWeakMap
key 类型任意只能是对象
遍历
size
内存安全

👉 WeakMap 是“生命周期绑定工具”,不是集合结构。


四、一个判断是否该用 WeakMap 的黄金法则

这份数据,是否应该“跟着某个对象一起消失”?

  • 如果是 👉 WeakMap / WeakSet
  • 如果不是 👉 Map / State / Store

这个判断,几乎不会出错。


五、WeakMap 在真实项目中的 6 个高价值场景

1️⃣ 对象私有数据(比 class 私有字段更安全)

const _state = new WeakMap();

class User {
  constructor(name) {
    _state.set(this, { name });
  }

  getName() {
    return _state.get(this).name;
  }
}
  • 外部无法访问
  • 实例销毁即回收

2️⃣ DOM / 组件实例缓存(高频内存泄漏点)

const elementCache = new WeakMap();

function bind(el) {
  elementCache.set(el, {
    listener: () => console.log('click'),
  });
}

📌 DOM 移除后:

  • WeakMap 自动清理
  • Map 会无限堆积

3️⃣ React / React Native 的“非状态数据”

const metaMap = new WeakMap();

function attachMeta(ref, meta) {
  metaMap.set(ref, meta);
}

适合:

  • 非 state
  • 非 props
  • 但要跟组件生命周期走的数据

4️⃣ RN 图片 / 视频元数据缓存(非常实用)

const imageMeta = new WeakMap();

function cacheAspectRatio(imageRef, ratio) {
  imageMeta.set(imageRef, ratio);
}

结合 FastImage / Video:

  • 组件卸载
  • 宽高比自动释放

👉 比全局 Map 安全太多。


5️⃣ 事件系统 / 观察者模式

const listeners = new WeakMap();

function addListener(target, fn) {
  listeners.set(target, fn);
}
  • target 消失
  • listener 不会泄漏

6️⃣ 计算结果缓存(比 LRU 更安全)

const memo = new WeakMap();

function heavyCalc(obj) {
  if (memo.has(obj)) return memo.get(obj);
  const res = compute(obj);
  memo.set(obj, res);
  return res;
}

👉 不需要考虑“什么时候清缓存”。


六、WeakSet:当你只关心“存在与否”

const visited = new WeakSet();

function walk(node) {
  if (visited.has(node)) return;
  visited.add(node);
}

适合:

  • 防止递归循环
  • 对象访问标记

七、3 个最常见的反模式(一定要避开)

❌ 1. 当普通 Map 用

wm.set({}, 1); // 可能立刻被 GC

key 没有强引用,值随时消失。


❌ 2. 依赖遍历 / size

WeakMap 设计上就不允许你这样做


❌ 3. 用来存全局状态

  • Redux
  • Zustand
  • 全局配置

👉 这些都不该用 WeakMap。


八、WeakMap vs Map:最终决策表

问题应该用
跟对象生命周期走WeakMap
需要遍历Map
状态管理Store
防内存泄漏WeakMap

九、一句话总结(记住它)

对象活,数据活;对象死,数据死。

满足这句话,直接用 WeakMap / WeakSet


写在最后

WeakMap 不是黑魔法,
它只是帮你把引用关系设计正确

写不泄漏的 JavaScript,是工程基本功。

欢迎评论区交流 👇