WeakRef 和 FinalizationRegistry 是什么?为什么不建议依赖它们写业务逻辑?
简化版
WeakRef 可以弱引用一个对象,不阻止它被垃圾回收。FinalizationRegistry 可以在对象被回收后注册一个清理回调。
它们适合做缓存、资源辅助清理等底层场景,但不适合承载核心业务逻辑,因为垃圾回收时机不确定,回调可能很晚执行,也可能在程序生命周期内不执行。
详细版
let user = { name: "Ada" };
const ref = new WeakRef(user);
console.log(ref.deref()?.name); // Ada
user = null;
// 之后某个不确定时间,对象可能被 GC
console.log(ref.deref()); // 可能是对象,也可能是 undefined
FinalizationRegistry:
const registry = new FinalizationRegistry((heldValue) => {
console.log("cleanup", heldValue);
});
let node = {};
registry.register(node, "node-cache");
node = null;
不能依赖它立刻释放文件、锁、订阅等关键资源。关键资源应显式关闭。
完整版教学
一、强引用和弱引用的区别
普通变量、数组、对象属性通常都是强引用。只要对象仍能从根对象访问到,垃圾回收器就不会回收它。
弱引用不会阻止回收。
strong ref -> object -> kept alive
weak ref -> object -> can be collected
WeakRef 的重点不是“更省内存”,而是“引用关系不影响对象生命周期”。
二、WeakRef 如何读取对象
WeakRef 通过 deref() 读取目标对象。
const ref = new WeakRef({ id: 1 });
const value = ref.deref();
if (value) {
console.log(value.id);
}
deref() 可能返回对象,也可能返回 undefined。所以每次使用前都要重新判断。
三、为什么 GC 时机不确定
垃圾回收由引擎决定,受内存压力、运行状态、实现策略影响。代码不能要求“某一行之后马上回收”。
数字例子:一个页面有 1000 个弱引用缓存项,即使其中 900 个已经没有强引用,浏览器也可能等到之后某个时机才回收,而不是立即清掉。
所以测试 WeakRef 行为时,不能写依赖确定 GC 顺序的断言。
四、FinalizationRegistry 的作用和限制
FinalizationRegistry 可以登记清理任务。
const registry = new FinalizationRegistry(key => {
cache.delete(key);
});
function remember(key, value) {
cache.set(key, new WeakRef(value));
registry.register(value, key);
}
它适合做辅助清理,但回调执行时间不确定,不能保证一定执行。
五、适合场景:弱缓存
弱缓存希望“有对象时复用,没有对象时允许回收”。
const imageCache = new Map();
function getCached(key) {
const ref = imageCache.get(key);
return ref?.deref();
}
如果 deref() 取不到,就重新创建。业务正确性不依赖弱引用对象一直存在。
六、不适合场景:关键资源释放
不要依赖 FinalizationRegistry 做这些事:
- 关闭 WebSocket
- 释放业务锁
- 取消订阅
- 保存用户数据
- 提交埋点
这些操作应该有显式生命周期。
| 资源 | 推荐释放方式 |
|---|---|
| 订阅 | unsubscribe() |
| 连接 | close() |
| 定时器 | clearTimeout() / clearInterval() |
subscription.unsubscribe();
socket.close();
七、常见误区与追问
- 误区:WeakRef 能手动控制垃圾回收。 它只是弱引用,不提供控制 GC 的能力。
- 误区:deref 只要成功一次就一直成功。 后续对象仍可能被回收,每次都要判断。
- 误区:FinalizationRegistry 回调会立即执行。 回调时机不确定,甚至可能不执行。
- 误区:弱引用适合保存核心业务状态。 核心状态不能依赖不确定生命周期。
- 追问:WeakMap 和 WeakRef 有什么区别? WeakMap 的键是弱引用,WeakRef 是直接弱引用一个对象。
- 追问:为什么弱缓存可以用 WeakRef? 因为缓存命中只是优化,未命中可以重新计算。
八、加强记忆
记住这条边界:
correctness depends on it -> do not use WeakRef
only performance cache -> can consider WeakRef
回答时先讲弱引用不阻止回收,再讲 deref() 可能取不到,最后强调 FinalizationRegistry 只能辅助清理,关键资源必须显式释放。