WeakRef 与 FinalizationRegistry 是 JavaScript 中用于与垃圾回收器(GC)协同工作的两个高级 API,它们不干预 GC 行为,但允许我们在 GC 发生之后获取“副作用”的钩子。本质上,它们的触发并非实时,而是受 GC 策略支配、时间不确定、不可预测的“异步响应机制”。
结论:WeakRef 与 FinalizationRegistry 的 GC 触发条件简述
- WeakRef 的“目标对象”只有在该对象“不可达”(Unreachable)时,才可能被 GC 回收。
- FinalizationRegistry 的清理回调只会在目标对象被 GC 回收之后的某个未来时刻异步调用。
- 两个 API 都不会强制触发 GC,也无法预测触发时机。
- 回收时机取决于宿主环境(如 V8)如何实现其 GC 策略:包括 GC 的时机、压力、内存紧张程度等。
WeakRef:对对象的“弱引用”
let obj = { name: "hello" };
let ref = new WeakRef(obj);
obj = null;
let derefValue = ref.deref(); // 有可能返回对象,也有可能已被GC回收后返回 undefined
GC 触发条件:
- WeakRef 的目标对象
obj必须处于不可达状态(Unreachable),即程序中不再有任何强引用。 - GC 并不会立刻销毁目标对象,只有在垃圾回收算法下一轮扫描判定它可以清理时,才会清除它。
- 即使 WeakRef 被创建,也不会“阻止” GC 清除该对象。
注意:
- 使用
.deref()获取对象后,如果此时 GC 没有清除它,你可以继续操作; - 但 你不能依赖
.deref()返回始终有效对象,因为它可能随时失效。
FinalizationRegistry:对象死亡之后的清理通知
const registry = new FinalizationRegistry((heldValue) => {
console.log('对象被回收:', heldValue);
});
let obj = { foo: "bar" };
registry.register(obj, "my-object");
obj = null; // 此后某个时间,GC 会清理 obj,并异步调用注册函数
GC 触发条件:
- 注册的对象必须在内存中彻底不可达,才可能被 GC 回收。
- 回收后,JS 引擎会在“GC完成后的某个时刻”,调用注册回调(不是同步执行!)。
- heldValue 不是目标对象,而是你设置的“替身值”,用于说明“哪个对象”被清除。
特别强调:
- FinalizationRegistry 的回调不能保证执行时机;
- 不保证马上执行、不保证一定执行,甚至在低内存压力时完全不会被调用;
- 它是一个“最佳努力”(best-effort)机制;
- 不能用于核心业务逻辑,适合资源清理(如关闭连接、注销回调等);
实际中的 GC 触发机制(以 V8 为例)
V8 引擎(Chrome 和 Node.js 使用)中的 GC 策略具有多级触发机制:
- Minor GC(新生代 GC):对象分配压力大或空间满了;
- Major GC(老年代 GC):对象长期存活,内存紧张时触发;
- Idle GC(空闲时 GC):浏览器空闲帧期间补回收;
- Low memory GC:系统内存吃紧时强制触发。
WeakRef 和 FinalizationRegistry 都不影响 GC 是否执行,它们的工作依赖于:
- 对象是否真的不可达;
- 下一轮 GC 是否扫描到它;
- 垃圾回收线程是否完成清理任务;
- 是否处于允许调度注册回调的主线程状态。
为什么不能依赖 WeakRef 或 FinalizationRegistry 来做逻辑控制?
因为:
- 它们不是 GC 的控制器,而是 GC 的旁观者;
- GC 本身不可预测,调度由宿主环境决定;
- FinalizationRegistry 的回调可能永远不会被执行,例如程序运行短时间内未触发 GC;
- 它们适合用于“非关键清理”:如释放缓存、移除监听器、日志记录等。
冷知识与陷阱
❌ 错误用法:指望 WeakRef 判断对象是否“还活着”
const ref = new WeakRef(obj);
if (ref.deref()) {
// 认为 obj 还活着?错!它可能刚好 GC 还没动手!
}
这不是“活着”的判断方法。它只是“此刻 GC 可能还没动手”。
✅ 正确用途示例:LRU 缓存弱引用条目
const cache = new Map();
function getData(id) {
const ref = cache.get(id);
const val = ref?.deref();
if (val) return val;
const newVal = loadData(id);
cache.set(id, new WeakRef(newVal));
return newVal;
}
当数据被垃圾回收,WeakRef 会自动无效,下一次访问时重新加载。
总结表:两者对比
| 特性 | WeakRef | FinalizationRegistry |
|---|---|---|
| 是否影响 GC | 否(不会阻止对象被 GC) | 否(对象仍会被正常 GC) |
| GC 触发条件 | 目标对象无强引用且被 GC 回收 | 同上 |
| 是否保证触发 | 不保证 | 不保证 |
| 回调是否同步 | 无回调 | 异步回调 |
| 应用场景 | 缓存、软引用 | 清理副资源、日志、释放钩子等 |
| 是否能判断对象“还活着” | 否 | 否 |
结语
WeakRef 与 FinalizationRegistry 是为应对现代复杂内存生命周期而生的工具。它们并不是让你掌控垃圾回收,而是允许你“在对象死亡之后做点事”。
使用时需牢记两个原则:
- 它们无法预测、控制或强制 GC;
- 它们只是 GC 行为之后的观察器,适合辅助优化而非核心逻辑。