弱引用

JavaScript 是有垃圾回收的语言,也就是引擎会自动管理内存中对象的分配和回收。当某个对象不再使用时,垃圾收集器就会释放它占用的内存,以便用于其他目的。

然而,在某些情况下,我们可能希望保持对某个对象的引用,但又不阻止它被当做垃圾回收,这种引用被称为弱引用

弱引用就是不会阻止对象被作为垃圾回收的引用。换句话说,如果指向某个对象的唯一引用是弱引用,垃圾回收器会释放该对象所占用的内存。

WeakRef

在 JavaScript 中,通过 WeakRef 类可以创建对象的弱引用,这个构造函数只有一个参数,即弱引用的目标对象:

let obj = { name: "Matt" };
let weakRef = new WeakRef(obj);

目标对象在实例化之后不能改变。要访问被弱引用的目标对象,使用其 deref() 方法。这个方法返回目标对象,或者在目标对象被当做垃圾回收后返回 undefined。

let obj = { name: "Matt" };
let weakRef = new WeakRef(obj);

console.log(weakRef.deref()); // {name: 'Matt'}
obj = null;

// 然后,浏览器将目标对象作为垃圾回收了
// 垃圾回收之后
console.log(weakRef.deref());

注意:如果运行前面的例子,不会看到浏览器垃圾回收目标对象,浏览器在执行垃圾回收前会尽可能等待更长时间,而开发工具的控制台也会阻止浏览器执行垃圾回收。

FinalizationRegistry

有时候,我们可能会在某个对象将要被垃圾回收时做一些清理或终结操作。这时候就可以使用 FinalizationRegistry 对象。使用 FinalizationRegistry 可以定义一个回调函数,这个函数将在注册的对象被回收之前执行。注册或注销对象可以使用 register()unregister() 方法。

要注册对象,将对象作为参数传给 register() 方法,另外再传一个 “持有值”(held value),这个值在垃圾回收前将被传给处理函数。

let obj = { name: "Matt" };

let finalizationRegistry = new FinalizationRegistry((heldValue) => {
  console.log(`Cleaning up object: ${heldValue}`);
});

finalizationRegistry.register(obj, "My held value");

obj = null;

// 垃圾回收之后
// "Cleaning up object: My held value"

要注销对象,调用 unregister() 并传入注册的对象:

let obj = { name: "Matt" };

let finalizationRegistry = new FinalizationRegistry((heldValue) => {
  console.log(`Cleaning up object: ${heldValue}`);
});

finalizationRegistry.register(obj, "My held value");
obj = null;
finalizationRegistry.unregister(obj);

// 垃圾回收之后
// 无输出

FinalizationRegistry 是一个需要谨慎对待的特性,应该尽可能不用:

  • JavaScript 中的垃圾回收行为是不能保证的,可能会因为引擎版本而异。
  • 不推荐在关键逻辑中使用清理回调,因为它们的执行的时机取决于 JavaScript 引擎的实现。
  • 主流的实现有可能在程序执行过程中执行清理回调,但可能在相关对象被回收之后很晚。
  • 某些情况下,比如完全停止程序执行,可能导致清理回调完全没有机会执行。