返回
创建于
状态公开

我们来讲更深入的应用与原理层面——包括内存语义、隐式绑定、DOM、代理(Proxy)集成、以及性能与陷阱。


💡 一、弱引用的底层原理(深入理解 GC)

WeakMap / WeakSet 的核心特性是 弱引用(weak reference)

📘 1. 什么是“弱引用”?

在 JavaScript 的垃圾回收机制中:

  • 强引用(strong reference):只要对象被引用,就不会被 GC;
  • 弱引用(weak reference):即使被引用,也不会阻止 GC
js
1let obj = { name: 'Alice' };
2const map = new Map();
3map.set(obj, 123);
4
5// obj = null; // 即使 null,map 还引用着它 → 不会被回收

但使用 WeakMap:

js
1const weakMap = new WeakMap();
2let obj = { name: 'Alice' };
3weakMap.set(obj, 123);
4obj = null; // ✅ 一旦没其他引用,对象会被 GC 清除

✅ WeakMap 不“持有”对象,只“知道”它存在与否。


🧩 二、WeakMap 的高级应用场景

1️⃣ 绑定私有状态 / 内部数据

一种常见模式:将对象实例与私有数据分离

js
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 节点附加数据而不污染元素本身

js
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 存储 对象与代理对象 的映射。

js
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 缓存惰性计算结果

可实现“对象缓存 + 自动清理”:

js
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 节点被重复绑定事件。

js
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️⃣ 配合类实例追踪

记录当前活动实例,不持有强引用:

js
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 时执行清理逻辑。
js
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手动控制弱引用对象释放回调、资源回收