JavaScript中的对象管理和事件清理

2024-02-15 22:29:34 浏览数 (2)

JavaScript作为一种垃圾回收语言,通常我们不必关心对象的分配和释放问题。但偶尔,在处理回调函数时,即使不再有任何有意义的引用,也很容易让对象永远保持活跃状态。

语言为我们提供了几种工具来处理这些情况:

  • WeakRef:用于存储对对象的单个弱引用
  • WeakMap:只要对象存在,就将值与对象关联起来
  • WeakSet:只要对象存在,就将其记住
  • FinalizationRegistry:当对象被收集时执行某些操作

根据情况,我们可能需要这些功能中的一个或另一个,但我今天想描述的情况将使用第一个和最后一个功能。

一个常见的情况是对象关心某些外部状态的变化,只要它们存在就要关注。例如,自定义元素可能希望在window对象上监听"scroll"事件。但是,简单地向window添加事件侦听器意味着保留对对象的引用。如果这些自定义元素的生命周期很短但数量很多,它们将在内存中累积,并且额外的事件侦听器也会堆积并浪费处理能力。

以下是一个类似情况的简单示例:

代码语言:javascript复制
class MyElement extends HTMLElement {
   constructor() {
      super()
      window.addEventListener("scroll", event => {
         this.handleScroll()
      })
   }

   handleScroll() {
      this.classList.toggle("top", window.scrollY == 0)
   }
}

我们希望在对象被垃圾回收时移除事件侦听器。为了实现这一点,我们可以利用两个特性:

首先,将事件侦听器中对this的强引用替换为WeakRef将阻止事件侦听器在没有其他引用存在时保持对象活跃。一旦对象被收集,deref()方法将返回undefined。

代码语言:javascript复制
const ref = new WeakRef(this)
window.addEventListener("scroll", event => {
   ref.deref()?.handleScroll()
})

这将允许对象被垃圾回收,但将保留事件侦听器附加,这意味着它仍将在每个滚动事件上触发,无法解除引用并因此什么也不做。

清理事件侦听器的一种简单方法是将AbortController与FinalizationRegistry结合使用。

前者让我们向事件传递一个信号,该信号将删除事件,而后者允许我们在某些对象被收集时运行一些代码。

这个接口相对基本:我们创建一个新的FinalizationRegistry并传递一个回调。然后,我们注册一个对象A和一个关联的(不同的)对象B。当A被垃圾回收时,显然无法将其传递给回调,因此回调会传递B。

代码语言:javascript复制
const abortRegistry = new FinalizationRegistry(c => c.abort())

现在,这个abortRegistry允许我们注册一个对象和一个关联的AbortController,并且每当对象被收集时,将调用controller的abort()方法。

现在我们只需要在创建时注册对象,并将控制器的信号传递给事件侦听器。

以下是完整的代码:

代码语言:javascript复制
const abortRegistry = new FinalizationRegistry(c => c.abort())

class MyElement extends HTMLElement {
   constructor() {
      super()
      const ref = new WeakRef(this)
      const controller = new AbortController()
      abortRegistry.register(this, controller)

      window.addEventListener("scroll", event => {
         ref.deref()?.handleScroll()
      }, { signal: controller.signal })
   }

   handleScroll() {
      this.classList.toggle("top", window.scrollY == 0)
   }
}

我正在参与2024腾讯技术创作特训营第五期有奖征文,快来和我瓜分大奖!

0 人点赞