← 返回题目列表

WeakRef 和 FinalizationRegistry 是什么?为什么不建议依赖它们写业务逻辑?

困难 第 29 / 29 题 更新于 2026/07/29
JavaScriptWeakRef垃圾回收

简化版

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 只能辅助清理,关键资源必须显式释放。