JavaScript的垃圾回收机制基于可达性分析,当一个对象无法被任何活跃引用访问到时,就会被标记为可回收。但默认的强引用会让对象只要被引用就无法回收,在某些场景下会导致不必要的内存占用。弱引用WeakRef和终结器FinalizationRegistry就是为了解决这个问题而出现的,它们让开发者可以更灵活地控制对象的生命周期。

弱引用WeakRef的基本原理
WeakRef可以创建一个对对象的弱引用,这个引用不会阻止对象被垃圾回收。和普通的强引用不同,即使存在WeakRef指向某个对象,只要该对象没有其他强引用,垃圾回收器就可以回收它。
创建WeakRef的方式很简单,只需要把目标对象作为参数传入WeakRef构造函数即可:
// 创建一个普通对象
const targetObj = { name: "test" };
// 创建该对象的弱引用
const weakRef = new WeakRef(targetObj);
// 通过deref方法获取弱引用指向的对象
// 如果对象还未被回收,返回对象本身;如果已经被回收,返回undefined
const derefResult = weakRef.deref();
console.log(derefResult); // 输出 { name: "test" }
需要注意的是,deref方法的返回结果是不确定的,因为垃圾回收的触发时机是不确定的。你无法主动触发垃圾回收,也无法知道对象是否已经被回收,只能通过调用deref来尝试获取对象。
终结器FinalizationRegistry的作用
FinalizationRegistry可以在注册的对象被垃圾回收之后,执行指定的回调函数。这个特性可以用来做一些清理工作,比如释放和对象关联的资源、记录对象被回收的日志等。
使用FinalizationRegistry需要先创建一个注册表实例,然后调用register方法注册需要监听的对象,还可以传入一个可选的持有值(heldValue)和可选的取消注册令牌(unregisterToken):
// 创建终结器注册表,传入对象被回收后执行的回调
const registry = new FinalizationRegistry((heldValue) => {
console.log(`对象被回收了,关联的持有值是:${heldValue}`);
});
// 创建目标对象
const obj = { id: 1 };
// 注册对象,第二个参数是持有值,会在回调中传入
registry.register(obj, "obj的持有值");
// 解除对obj的强引用,让它可以成为垃圾回收的目标
// 注意:这里只是示例,实际中需要确保所有强引用都被移除
// obj = null;
如果需要取消注册,可以调用unregister方法,传入之前注册时使用的取消注册令牌。如果没有传入取消注册令牌,就无法取消注册。
两者的配合使用场景
WeakRef和FinalizationRegistry经常配合使用,实现一些需要跟踪对象生命周期的功能。最常见的场景是实现一个不会阻止对象回收的缓存:
class WeakCache {
constructor() {
// 缓存映射,key是缓存键,value是WeakRef
this.cache = new Map();
// 终结器注册表,对象被回收时清理缓存
this.registry = new FinalizationRegistry((key) => {
this.cache.delete(key);
console.log(`缓存键${key}对应的对象已被回收,缓存已清理`);
});
}
set(key, value) {
// 创建弱引用存入缓存
const weakRef = new WeakRef(value);
this.cache.set(key, weakRef);
// 注册对象到终结器,对象回收时清理对应缓存
this.registry.register(value, key);
}
get(key) {
const weakRef = this.cache.get(key);
if (!weakRef) return undefined;
// 尝试获取对象
const value = weakRef.deref();
if (!value) {
// 对象已被回收,清理缓存
this.cache.delete(key);
return undefined;
}
return value;
}
}
// 使用示例
const cache = new WeakCache();
const user = { name: "张三" };
cache.set("user1", user);
console.log(cache.get("user1")); // 输出 { name: "张三" }
// 解除强引用,等待垃圾回收
// user = null;
使用限制和注意事项
- 垃圾回收的触发时机是完全不确定的,你无法预测WeakRef指向的对象什么时候会被回收,也无法主动触发回收,因此不要依赖deref的结果来做关键逻辑判断。
- FinalizationRegistry的回调执行时机也是不确定的,而且不同的JavaScript引擎可能有不同的实现,不要假设回调会在对象被回收后立即执行。
- 不要在终结器的回调中访问被回收的对象,也不要在回调中做太复杂的操作,避免影响性能。
- WeakRef和FinalizationRegistry都不适合用在需要精确控制内存的场景,它们只是提供了更灵活的内存管理方式,不能替代合理的内存使用习惯。
和其他弱引用类型的区别
JavaScript中还有WeakMap和WeakSet这两种弱引用集合,它们的键是弱引用,不会阻止键对象被回收。和WeakRef、FinalizationRegistry的区别如下:
| 类型 | 作用 | 适用场景 |
|---|---|---|
| WeakMap | 键为弱引用的键值对集合,键被回收后对应条目自动移除 | 给对象添加临时关联数据,不阻止对象回收 |
| WeakSet | 元素为弱引用的集合,元素被回收后自动从集合中移除 | 跟踪对象是否存在,不阻止对象回收 |
| WeakRef | 创建单个对象的弱引用,可随时获取对象引用 | 需要单独引用某个对象且不阻止其回收的场景 |
| FinalizationRegistry | 对象被回收后执行清理回调 | 对象回收后的资源释放、缓存清理等 |
总的来说,WeakRef和FinalizationRegistry为JavaScript的内存管理提供了更细粒度的控制能力,合理使用可以避免一些不必要的内存泄漏,提升应用的性能。但也要注意它们的不确定性,不要过度依赖这两个特性,优先还是要从代码结构层面做好内存管理。
WeakRefFinalizationRegistryJavaScript内存管理修改时间:2026-06-20 08:57:32