写在前面:
如果你写过 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 的“简化版”
这是一个很多人会踩的认知误区。
| 能力 | Map | WeakMap |
|---|---|---|
| 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,是工程基本功。
欢迎评论区交流 👇