移除事件监听器的几种选项

575 阅读5分钟

有多种选项可用于移除事件监听器

在JavaScript中,回顾一些常见的方法来移除事件监听器。

在构建高效、可预测的应用程序时,代码在运行时的清理是不可妥协的一部分。在JavaScript中,有一种方法是通过恰当管理事件监听器来实现,尤其是在不再需要它们时将其移除。

有几种方法可用于实现这一点,每种方法都有一套权衡,使其在特定情况下更合适。我们将介绍一些最常用的策略,以及在决定在任何给定时间哪种方法最适合工作时需要考虑的一些因素。

我们将使用以下设置进行实验——一个带有单个click事件监听器的按钮:

    <button id="button">做点什么</button>

    <script>
    document.getElementById('button').addEventListener('click', () => {
        console.log('点击了!');
    });

使用Chrome的 getEventListeners() 函数,您将看到一个监听器附加到该元素上:

图片.png

如果您需要删除该监听器,以下是您可能会使用的方法。

使用.removeEventListener()

这可能是最明显的方法,但也可能对您的理智构成最大威胁。.removeEventListener()方法接受三个参数:要删除的监听器类型,该监听器的回调函数和一个选项对象。

但问题在于:这些确切的参数必须_完全_匹配在设置监听器时使用的参数,包括在内存中对回调的_相同引用_。否则,.removeEventListener()将不起作用。

考虑到这一点,下面的方法将完全无效:

document.getElementById('button').addEventListener('click', () => {
    console.log('点击了!');
});

document.getElementById('button').removeEventListener('click', () => {
    console.log('点击了!');
});

尽管该回调函数_看起来_与最初附加的回调函数完全相同,但它们不是_相同的引用_。解决此问题的方法是将回调函数设置为变量,并在.addEventListener().removeEventListener()中引用它。

const myCallback = () => {
    console.log('点击了!');
};

document.getElementById('button').addEventListener('click', myCallback);
document.getElementById('button').removeEventListener('click', myCallback);

或者,对于特定用例,您还可以通过在函数本身内部引用伪匿名函数来删除侦听器:

document
  .getElementById('button')
  .addEventListener('click', function myCallback() {
    console.log('点击了!');

    this.removeEventListener('click', myCallback);
  });

尽管它很特殊,但.removeEventListener()的优点在于其目的非常明确。在阅读代码时,您对它在做什么毫无疑问。

使用.addEventListener()once选项

.addEventListener()方法带有一个工具,用于帮助在预期仅使用一次的情况下自我清理:once选项。它与其听起来的一样简单。如果将其设置为true,则在首次调用后,监听器将自动删除:

const button = document.getElementById('button');

button.addEventListener('click', () => {
    console.log('点击了!');
}, { once: true });

// '点击了!'
button.click();

// 不再有监听器!
getEventListeners(button) // {} 

如果适用于您的用例,如果监听器仅需要被调用一次,这种方法可能是合适的,尤其是对于使用匿名函数的情况。

克隆并替换节点

有时,您可能不知道给定节点上的所有活动监听器,但您确实知道您想要删除它们。在这种情况下,可以克隆整个节点并用该克隆替换自身。使用.cloneNode() 方法,通过.addEventListener()附加的任何监听器都不会传递,从而使其处于干净状态。

在JavaScript客户端的旧时代,您可能会通过查询到父节点,并将特定子节点替换为克隆来执行此操作:

button.parentNode.replaceChild(button.cloneNode(true), button);

但在现代浏览器中,可以使用.replaceWith()来简化:

button.replaceWith(button.cloneNode(true));

这里唯一可能让您困惑的事情是,内部监听器是_保留的,_这意味着具有onclick属性的按钮仍将按照定义的方式触发:

<button id="button" onclick="console.log('点击了!')">
    做点什么
</button>

总之,如果您需要以强制手段不加选择地删除任何类型的监听器,这是一个值得考虑的选项。然而,不足之处在于,它在其目的方面不够明显。有些人甚至可能称其为“hack”。

使用AbortController()

这个对我来说是新的。我在偶然间看到了这条推文 ,才刚刚了解它。如果您和我一样,可能只听说过AbortController被用于取消 fetch() 请求。但它显然比那更灵活。

最近,.addEventListener()可以通过一个信号进行配置,以便命令式地中止/删除一个监听器。当相应的控制器调用.abort()时,该信号将触发要删除的监听器:

const button = document.getElementById('button');
const controller = new AbortController();
const { signal } = controller;

button.addEventListener('click', () => console.log('点击了!'), { signal });

// 删除监听器!
controller.abort();

这种方法最明显的优点可能是人机工程学。在我看来,这是一种更清晰的方式,可以删除一个监听器,而不用担心处理.removeEventListener()时可能遇到的问题。但也有一个更战术性的优点:您可以使用一个信号来一次性删除多个监听器,无论是任何类型的监听器。而且,使用匿名函数也是完全可以的:

const button = document.getElementById('button');
const controller = new AbortController();
const { signal } = controller;

button.addEventListener('click', () => console.log('点击了!'), { signal });
window.addEventListener('resize', () => console.log('调整大小!'), { signal });
document.addEventListener('keyup', () => console.log('按键!'), { signal });

// 一次性删除所有监听器:
controller.abort();

唯一需要犹豫的是浏览器的支持情况。这是一个相对较新的功能,只有在Chrome的支持中,自2021年(v90)开始完全支持。因此,如果您需要支持几年前的浏览器版本,要牢记这一点。

应该选择哪个?

与其他事物一样,“取决于情况”。尽管如此,在一刹那的时候,以下是我可能如何选择其中一种方法的方法:

  • 如果回调函数分配给变量并且在添加监听器的位置容易获得,请使用.removeEventListener()
  • 如果需要仅调用一次回调函数,请在.addEventListener()中使用once选项(显然)。
  • 如果需要在一次性删除多个监听器,请使用克隆和替换方法。
  • 如果您想要命令性地一次性删除一系列监听器,或者您只是喜欢这种语法,请使用AbortController()

还缺少什么?

我可能漏掉了其他方法。如果您知道其他方法,请随意发表评论或以其他方式让我知道。