TypeScript/JavaScript:此WeakMap‘技巧’能否达到预期效果?
用WeakMap维护类实例并支持垃圾回收的正确姿势
首先直接给结论:如果你的Person实例不是WeakMap的键,那这个方案完全不可行。
WeakMap的弱引用特性仅针对它的键——只有当键是对象时,WeakMap才不会阻止该对象被垃圾回收。如果实例是存在WeakMap的值里,WeakMap会对值保持强引用,和用普通数组存实例没有任何区别,实例依然无法被垃圾回收。
正确实现思路:结合WeakRef与FinalizationRegistry
要实现「既能追踪所有存活实例,又不阻止垃圾回收」的需求,单纯用WeakMap/WeakSet不够,因为它们不支持遍历操作。ES2021引入的WeakRef(弱引用)和FinalizationRegistry(终结器)可以完美解决这个问题:
- 用
WeakRef存储实例的弱引用,不会阻止实例被回收 - 用
FinalizationRegistry监听实例的回收事件,自动清理失效的弱引用 - 维护一个普通集合存储
WeakRef,遍历的时候过滤出还存活的实例
代码示例
class Person { constructor(name) { this.name = name; InstanceTracker.register(this); } } class InstanceTracker { // 存储实例的WeakRef,方便后续遍历 static #weakRefs = new Set(); // 终结器:实例被回收时自动清理对应的WeakRef static #cleanupRegistry = new FinalizationRegistry(weakRef => { InstanceTracker.#weakRefs.delete(weakRef); }); // 注册实例到追踪系统 static register(instance) { const ref = new WeakRef(instance); InstanceTracker.#weakRefs.add(ref); // 关联实例与WeakRef,实例回收时触发清理 InstanceTracker.#cleanupRegistry.register(instance, ref); } // 获取所有当前存活的实例 static getAliveInstances() { const aliveInstances = []; // 遍历WeakRef集合,过滤出还存活的实例,同时清理失效引用 for (const ref of InstanceTracker.#weakRefs) { const instance = ref.deref(); if (instance) { aliveInstances.push(instance); } else { InstanceTracker.#weakRefs.delete(ref); } } return aliveInstances; } }
测试验证方法
Node.js环境测试(需手动触发GC)
- 启动Node.js时添加
--expose-gc参数,允许手动调用垃圾回收 - 执行以下代码:
// 创建实例 let alice = new Person('Alice'); let bob = new Person('Bob'); console.log(InstanceTracker.getAliveInstances().length); // 输出 2 // 解除所有强引用 alice = null; bob = null; // 手动触发垃圾回收 global.gc(); console.log(InstanceTracker.getAliveInstances().length); // 输出 0
浏览器环境测试
- 打开浏览器控制台,创建实例:
let testPerson = new Person('Test'); - 打开DevTools的「Memory」面板,拍摄一次堆快照,搜索
Person,能看到该实例 - 解除引用:
testPerson = null; - 点击Memory面板的垃圾桶按钮触发垃圾回收,再拍一次快照
- 再次搜索
Person,应该找不到该实例;同时调用InstanceTracker.getAliveInstances()会返回空数组
内容的提问来源于stack exchange,提问作者user3767443
相关产品推荐
相关产品推荐

