如何实现WeakTupleSet?使元组随元素外部引用回收自动移除
实现WeakTupleSet:自动回收包含可被垃圾回收元素的元组
我们需要实现一个WeakTupleSet,功能类似现有的TupleSet(存储顺序无关的唯一元组),但核心区别是:当元组中任意一个元素在集合外部不再被引用时,该元组会自动从集合中移除,对应的元素也能被垃圾回收机制处理。
实现核心思路
- 用
WeakRef包装元组中的每个元素,避免强引用导致元素无法被回收 - 借助
FinalizationRegistry监听元素的垃圾回收事件,一旦元素被回收,立即清理集合中所有包含该元素的元组 - 存储结构记录元组长度、元素的WeakRef集合,同时保留元组唯一性判断逻辑(长度相同且元素完全匹配,顺序无关)
- 每次执行操作前,先过滤掉已存在回收元素的无效元组
代码实现
export class WeakTupleSet { // 存储元组的弱引用结构:长度、元素WeakRef数组、清理令牌 private tupleEntries = new Set<{ length: number; weakRefs: WeakRef<unknown>[]; cleanupToken: symbol; }>(); // 元素回收监听器,触发时清理关联元组 private finalizationRegistry = new FinalizationRegistry((cleanupToken: symbol) => { this.tupleEntries.forEach(entry => { if (entry.cleanupToken === cleanupToken) { this.tupleEntries.delete(entry); } }); }); // 检查目标元组与存储条目是否匹配 #isMatchingTuple(targetTuple: unknown[], entry: typeof this.tupleEntries extends Set<infer T> ? T : never): boolean { if (targetTuple.length !== entry.length) return false; // 过滤出仍存活的元素 const aliveElements = entry.weakRefs.map(ref => ref.deref()).filter(val => val !== undefined); // 有元素已回收则直接不匹配 if (aliveElements.length !== entry.length) return false; // 验证目标元组所有元素都在存活列表中 for (const val of targetTuple) { if (!aliveElements.includes(val)) return false; } return true; } add(tuple: unknown[]): boolean { this.#cleanupInvalidEntries(); // 检查是否已有匹配元组 for (const entry of this.tupleEntries) { if (this.#isMatchingTuple(tuple, entry)) { return false; } } // 创建弱引用条目并注册回收监听 const weakRefs = tuple.map(item => new WeakRef(item)); const cleanupToken = Symbol(); tuple.forEach(item => { this.finalizationRegistry.register(item, cleanupToken); }); this.tupleEntries.add({ length: tuple.length, weakRefs, cleanupToken }); return true; } delete(tuple: unknown[]): boolean { this.#cleanupInvalidEntries(); for (const entry of this.tupleEntries) { if (this.#isMatchingTuple(tuple, entry)) { this.tupleEntries.delete(entry); // 取消该元组元素的回收监听(性能优化) entry.weakRefs.forEach(ref => { const val = ref.deref(); if (val) this.finalizationRegistry.unregister(entry.cleanupToken); }); return true; } } return false; } has(tuple: unknown[]): boolean { this.#cleanupInvalidEntries(); for (const entry of this.tupleEntries) { if (this.#isMatchingTuple(tuple, entry)) { return true; } } return false; } // 清理所有包含已回收元素的无效元组 #cleanupInvalidEntries(): void { this.tupleEntries.forEach(entry => { const hasCollected = entry.weakRefs.some(ref => ref.deref() === undefined); if (hasCollected) { this.tupleEntries.delete(entry); } }); } }
使用示例
const set = new WeakTupleSet(); globalThis.obj1 = {}; globalThis.obj2 = {}; globalThis.obj3 = {}; const tuple = [globalThis.obj1, globalThis.obj2, globalThis.obj3]; set.add(tuple); console.log(set.has(tuple)); // 输出: true // 取消对obj2的外部引用 tuple[1] = undefined; delete globalThis.obj2; // 手动触发垃圾回收(仅Node.js加--expose-gc参数或浏览器开发者工具有效) if (globalThis.gc) globalThis.gc(); // 等待GC自动执行后,元组会被移除 setTimeout(() => { console.log(set.has(tuple)); // 输出: false }, 1000);
注意事项
FinalizationRegistry的回调执行时机由垃圾回收机制决定,无法精确控制- 元组元素必须是可被垃圾回收的对象类型,原始值(如字符串、数字)无法被
WeakRef包装,不适用此实现 - 生产环境无需手动调用
gc(),依赖浏览器/Node.js的自动垃圾回收逻辑
内容的提问来源于stack exchange,提问作者trusktr
相关产品推荐
相关产品推荐

