WeakRef 仅解决对象能否被 GC,不提供过期能力;需结合 FinalizationRegistry 清理 key、手动维护时间戳实现 TTL,并在 set 前 unregister 旧注册以防重复触发。

WeakRef 本身不提供过期能力,也不能替代 TTL 或 LRU 逻辑;它只解决“对象能否被 GC”的问题。真正的过期策略必须由你主动控制——比如在 FinalizationRegistry 回调里删 key,或在每次 get() 时检查时间戳。
WeakRef + Map 构建基础资源池,但必须手动管理键生命周期
很多人一上来就用 new WeakRef(value) 包裹资源,再塞进 Map,以为这样就能“自动释放”。错。WeakRef 实例本身是强引用,只要它还挂在 Map 里,就不会被 GC;而 value 被回收后,weakRef.deref() 返回 undefined,但 Map 里仍存着这个“空壳”,查找变慢、内存反升。
正确做法是:每次 set(key, value) 时,同步做三件事:
- 用
new WeakRef(value)创建弱引用 - 把
key → weakRef存入Map - 立即调用
registry.register(value, key, weakRef),其中weakRef是注销凭证,确保将来能精准取消注册
FinalizationRegistry 回调里只能删 key,不能碰原对象
FinalizationRegistry 的回调不是“对象快死了来提醒你”,而是“对象已被 GC,此刻已不可访问”。你唯一能做的,就是清理外部状态,比如从缓存中删除对应 key。
常见错误包括:
- 在回调里尝试访问
value或调用weakRef.deref()—— 此时必然返回undefined,且可能触发异常 - 注册时传入的
heldValue是普通对象(如{id: 1}),导致后续无法匹配 key、清理失败;必须用string或symbol - 没传
unregisterToken(即weakRef实例),或复用同一 registry 却传错 token,结果一个对象回收触发多次清理,或根本没触发
加入 TTL 过期需额外时间戳字段,WeakRef 不感知时间
WeakRef 和 FinalizationRegistry 都不关心时间。要实现“5 分钟未访问则过期”,你得自己记录时间:
- 在
set()时,往 Map 里存一个带时间戳的包装对象:{ ref: weakRef, timestamp: Date.now() } - 在
get()时,先ref.deref(),再检查Date.now() - entry.timestamp > 5 * 60 * 1000,超时则delete并返回undefined - 不要依赖
setTimeout定时清理——浏览器后台标签页会冻结定时器,且大量setTimeout会拖慢性能
资源池真正难的不是“存”,而是“何时删 key”
最易被忽略的点是:同一个 key 可能被反复 set(),旧的 WeakRef 和注册还没清理,新的又注册进来。这会导致重复触发回调,甚至误删还在用的资源。
安全做法是:每次 set(key, value) 前,先查 Map 是否已有该 key,若有,先调用 registry.unregister(unregisterToken) 把旧注册注销掉,再存新 ref、新注册。
另外,FinalizationRegistry 回调执行时机不确定,可能延迟几秒甚至更久,不能用于实时性要求高的逻辑;TTL 检查必须放在 get() 入口,不能只靠 GC 触发。











