FinalizationRegistry 提供异步回调机制,在对象被垃圾回收后清理外部资源,不阻止 GC 且不可预测执行时机,需配合显式销毁使用。

现代 JavaScript 中的 FinalizationRegistry 允许你在对象被垃圾回收器(GC)回收后,**异步触发自定义回调**。它不是“析构函数”,也不保证立即执行,而是提供一种弱引用 + 回调的机制,用于清理外部资源(如手动分配的内存、文件句柄、WebGL纹理等),避免内存泄漏。
核心原理:弱引用 + 注册表轮询
FinalizationRegistry 本身不阻止 GC,它只监听你注册过的对象是否已被回收。JS 引擎在每次垃圾回收后,会检查哪些注册对象已不可达,然后将它们加入内部队列;之后在某个时机(非同步、不可预测)调用你提供的回调,并传入你注册时指定的“hold value”(保持值)——这个值不会阻止目标对象被回收。
- 注册的对象(target)必须是普通 JS 对象,不能是原始值
- hold value 可以是任意值(常用来存资源 ID、句柄、标识符等),它会被保留直到回调执行
- 回调执行时,target 已经不可访问,只能靠 hold value 做清理
- 无法知道 target 何时被回收,也不能强制触发回收
基本用法:注册 + 清理
下面是一个典型场景:为每个创建的 CanvasTexture 分配一个 ID,用 FinalizationRegistry 在其被回收时自动释放底层 GPU 资源:
详细的 Three.js 3D 图形参考,涵盖场景设置、相机、几何体、材质、光照、动画、控制器、加载器、数学工具和调试。
const registry = new FinalizationRegistry((heldValue) => {
console.log('清理资源:', heldValue);
// 例如:gl.deleteTexture(heldValue);
});
class Texture {
constructor(gl, id) {
this.gl = gl;
this.id = id;
// 注册 this(target)和对应的 id(hold value)
registry.register(this, id, this);
}
}
// 使用
const tex = new Texture(gl, 123);
tex = null; // 原对象可被回收
// 后续某次 GC 后,registry 回调可能被调用,输出 "清理资源: 123"
关键注意事项
这个 API 容易误用,必须注意以下几点:
-
不能依赖回调时机:它可能延迟数秒甚至更久,或在页面卸载前都不执行。仅适合“尽力而为”的清理,关键资源仍需显式
.destroy() - hold value 不要引用 target:否则会形成强引用链,阻止 GC —— 这是常见错误
- register 的第三个参数(unregister token)可选但推荐:用于提前取消注册(比如手动销毁时),避免重复清理
-
回调中无法访问原对象:
this是 undefined,参数只有 hold value;不要试图恢复或检查 target
配合显式销毁的健壮模式
最佳实践是“显式销毁 + finalizer 保底”:
const registry = new FinalizationRegistry((id) => {
cleanupGPUResource(id);
});
class Texture {
constructor(gl, id) {
this.gl = gl;
this.id = id;
registry.register(this, id, this); // 用 this 当 token
}
destroy() {
if (this.id != null) {
cleanupGPUResource(this.id);
registry.unregister(this); // 提前注销,防止重复清理
this.id = null;
}
}
}
这样既支持主动释放,又能在忘记调用 destroy() 时兜底。


















