返回
创建于
状态公开
我们来讲更深入的应用与原理层面——包括内存语义、隐式绑定、DOM、代理(Proxy)集成、以及性能与陷阱。
💡 一、弱引用的底层原理(深入理解 GC)
WeakMap / WeakSet 的核心特性是 弱引用(weak reference)。
📘 1. 什么是“弱引用”?
在 JavaScript 的垃圾回收机制中:
- 强引用(strong reference):只要对象被引用,就不会被 GC;
- 弱引用(weak reference):即使被引用,也不会阻止 GC。
1let obj = { name: 'Alice' };
2const map = new Map();
3map.set(obj, 123);
4
5// obj = null; // 即使 null,map 还引用着它 → 不会被回收但使用 WeakMap:
1const weakMap = new WeakMap();
2let obj = { name: 'Alice' };
3weakMap.set(obj, 123);
4obj = null; // ✅ 一旦没其他引用,对象会被 GC 清除✅ WeakMap 不“持有”对象,只“知道”它存在与否。
🧩 二、WeakMap 的高级应用场景
1️⃣ 绑定私有状态 / 内部数据
一种常见模式:将对象实例与私有数据分离。
1const privates = new WeakMap();
2
3function createUser(name, age) {
4 const user = {};
5 privates.set(user, { name, age });
6 return user;
7}
8
9function getAge(user) {
10 return privates.get(user).age;
11}
12
13let u = createUser('Alice', 20);
14console.log(getAge(u)); // 20
15u = null; // ✅ GC 自动清理🧠 优势:
- 无需手动清理;
- 不暴露内部实现;
- 防止内存泄漏。
2️⃣ DOM 元素附加元数据
前端常见用法:给 DOM 节点附加数据而不污染元素本身
1const elementData = new WeakMap();
2
3function bindData(el, data) {
4 elementData.set(el, data);
5}
6
7function getData(el) {
8 return elementData.get(el);
9}
10
11const div = document.createElement('div');
12bindData(div, { clicked: false });
13
14div.addEventListener('click', () => {
15 const d = getData(div);
16 d.clicked = true;
17});✅ 一旦
div被移出 DOM、无引用存在,elementData自动清除。 无需手动管理或调用delete(),非常适合框架底层。
3️⃣ WeakMap + Proxy = 响应式系统
现代框架(如 Vue3、MobX、Immer)会使用 WeakMap 存储 对象与代理对象 的映射。
1const reactiveMap = new WeakMap();
2
3function reactive(obj) {
4 if (reactiveMap.has(obj)) return reactiveMap.get(obj);
5
6 const proxy = new Proxy(obj, {
7 get(target, key, receiver) {
8 console.log(`读取 ${key}`);
9 return Reflect.get(target, key, receiver);
10 }
11 });
12
13 reactiveMap.set(obj, proxy);
14 return proxy;
15}
16
17const data = { name: 'Alice' };
18const proxy = reactive(data);
19console.log(proxy.name);✅
data被释放后,WeakMap 自动清除proxy映射,避免泄漏。 ⚙️ 这是 Vue3 的响应式系统核心机制之一。
4️⃣ WeakMap 缓存惰性计算结果
可实现“对象缓存 + 自动清理”:
1const heavyCache = new WeakMap();
2
3function getComputed(obj) {
4 if (!heavyCache.has(obj)) {
5 const value = expensiveCompute(obj);
6 heavyCache.set(obj, value);
7 }
8 return heavyCache.get(obj);
9}
10
11function expensiveCompute(obj) {
12 console.log('💭 计算中...');
13 return obj.id * 2;
14}
15
16let item = { id: 10 };
17console.log(getComputed(item)); // 计算中...
18console.log(getComputed(item)); // 缓存返回
19item = null; // 自动释放🌱 三、WeakSet 的高级场景
1️⃣ 防止重复处理(对象唯一访问)
例如:防止同一 DOM 节点被重复绑定事件。
1const processed = new WeakSet();
2
3function initElement(el) {
4 if (processed.has(el)) return; // 已处理
5 processed.add(el);
6 el.addEventListener('click', () => console.log('clicked'));
7}2️⃣ 配合类实例追踪
记录当前活动实例,不持有强引用:
1const activeInstances = new WeakSet();
2
3class Connection {
4 constructor() {
5 activeInstances.add(this);
6 }
7 close() {
8 activeInstances.delete(this);
9 }
10}
11
12function isActive(conn) {
13 return activeInstances.has(conn);
14}✅ 当连接对象被释放后,
WeakSet自动清理。
🔍 四、WeakMap 与 WeakRef + FinalizationRegistry 的结合
在更底层(Node.js / 浏览器新标准),我们可以结合:
WeakRef:允许临时访问弱引用对象;FinalizationRegistry:允许在对象被 GC 时执行清理逻辑。
1const registry = new FinalizationRegistry((key) => {
2 console.log(`对象 ${key} 已被回收`);
3});
4
5function trackObject(obj, key) {
6 const ref = new WeakRef(obj);
7 registry.register(obj, key);
8 return ref;
9}
10
11let obj = { data: 'test' };
12const ref = trackObject(obj, 'myObj');
13
14obj = null; // 触发 GC 时,会打印 “对象 myObj 已被回收”🔬 WeakMap / WeakSet 是“自动清除式”弱引用集合; 而 WeakRef / FinalizationRegistry 是“可观察式”弱引用机制。
⚙️ 五、陷阱与注意点
| 陷阱 | 说明 |
|---|---|
| ❌ 无法遍历 | WeakMap/WeakSet 无法获取全部键值,只能 has/get/delete |
| ❌ 不可序列化 | JSON.stringify() 不支持 |
| ❌ 非稳定引用 | 对象被 GC 后,数据直接消失 |
| ⚠️ 调试困难 | 无法直接查看内部存储(浏览器调试中通常为空) |
| ✅ 优点 | 自动释放,不会内存泄漏,适合中间层逻辑或缓存系统 |
🧭 六、总结
| 概念 | 功能 | 应用 |
|---|---|---|
| WeakSet | 跟踪对象存在性 | 防重复访问、活动对象管理 |
| WeakMap | 对象 → 数据的映射 | 私有属性、缓存、Proxy 系统、DOM 元数据 |
| WeakRef / FinalizationRegistry | 手动控制弱引用 | 对象释放回调、资源回收 |