如何移除事件监听器

在运行时清理你的代码是构建高效、可预测的应用程序,没有商量余地的部分 。在JavaScript中 , 实现这一目标的方法之一是很好地管理事件监听器 , 尤其是当不再需要时移除它们 。
有好几种方法可以做到这件事情 , 每种都有自己的一套权衡方法 , 使其在某些情况下更合适 。我们将介绍几种最常用的策略,以及当你试图决定哪种方法最适合于任何特定时间的工作时,需要考虑的一些问题 。
我们将对下面的设置进行修补–一个带有单击事件监听器的按钮:
<button id="button">Do Something</button><script>document.getElementById('button').addEventListener('click', () => { console.log('clicked!');});</script>使用getEventListeners()函数,你会看到只有一个监听器连接到该元素:

如何移除事件监听器

文章插图
如果你需要移除该监听器,你可以用以下几个方法 。
使用.removeEventListener()这可能是最显而易见的,但也是最有可能威胁到你心智的一个 。.removeEventListener()方法接收三个参数:待移除监听器的类型,监听器的回调函数,以及可选对象 。
但这里有一个(潜在的)棘手的部分:这些确切的参数必须与设置监听器时使用的参数完全一致,包括内存中回调的相同引用 。否则,.removeEventListener()啥也不做 。
考虑到这一点 , 下面的示例将是完全无效的:
document.getElementById('button').addEventListener('click', () => { console.log('clicked!');});document.getElementById('button').removeEventListener('click', () => { console.log('clicked!');});尽管回调函数看起来一样,但它们不是相同的引用 。解决方案是将回调函数设置为一个变量,并在.addEventListener().removeEventListener()中引用它 。
const myCallback = () => {console.log('clicked!');};document.getElementById('button').addEventListener('click', myCallback);document.getElementById('button').removeEventListener('click', myCallback);或者,对于特定的用例 , 你也可以通过在函数本身中引用一个伪匿名函数来移除监听器:
document.getElementById('button').addEventListener('click', function myCallback() {console.log('clicked!');this.removeEventListener('click', myCallback);});尽管有其特殊性,.removeEventListener()的优势在于其目的非常明确 。当你通读完代码时,对它的作用没有任何疑问 。
使用.addEventListener()的once选项如果.addEventListener()是为了一次性使用,.addEventListener()方法自带一个工具可以帮助自己清理:once选项 。这和它听起来一样简单 。如果设置为true , 监听器会在第一次被调用后自动移除它自己:
const button = document.getElementById('button');button.addEventListener('click', () => { console.log('clicked!');}, { once: true });// 'clicked!'button.click();// No more listeners!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('clicked!')"> Do Something</button>总之,如果你需要用蛮力不分青红皂白地删除任何种类的监听器 , 这是一个值得一试的选择 。然而,在缺点方面,就是它的目的不太明显 。有人会说它是一个hack手段 。
使用AbortController()该方法对我来说是新的 。我是在看到Caleb Porzio的这条推文时才知道的 。如果你和我一样 , 你可能只听说过AbortController是用来取消fetch()请求的 。但显然 , 它比这更灵活 。
最近,.addEventListener()可以设置一个signal,用于终止/移除一个监听器 。当相应的控制器调用.abort()时 , 该信号将触发监听器被删除:
const button = document.getElementById('button');const controller = new AbortController();const { signal } = controller;button.addEventListener('click', () => console.log('clicked!'), { signal });// Remove the listener!controller.abort();这样做最明显的好处可能是符合人体工程学 。它(在我看来)是一种更清晰的移除监听器的方式,而不用处理.removeEventListener()的潜在麻烦 。但也有一个更具战术性的优势:你可以使用一个信号来一次性移除多个任何类型的监听器 。而且使用匿名函数也是完全可以的:
const button = document.getElementById('button');const controller = new AbortController();const { signal } = controller;button.addEventListener('click', () => console.log('clicked!'), { signal });window.addEventListener('resize', () => console.log('resized!'), { signal });document.addEventListener('keyup', () => console.log('pressed!'), { signal });// Remove all listeners at once:controller.abort();唯一让人犹豫不决的原因是浏览器支持 。这是一个相对较新的功能,自2021年(v90)以来,Chrome浏览器才全面支持 。因此,如果你需要支持超过有几年历史的浏览器版本 , 请记住这一点 。
应该使用哪个【如何移除事件监听器】跟其他事情一样,这取决于实际使用场景:
  • 使用.removeEventListener():如果回调函数被赋值给一个变量,并且在监听器被添加的地方很容易找到时可以使用该方式 。
  • 使用once选项:如果你只需要触发一次回调时,可以使用该方式 。
  • 使用克隆和替换方法:如果你需要一股脑销毁多个监听器时,可以使用该方式 。
  • 使用AbortController():如果你有一系列的监听器想一次性地删除,或者你只是喜欢这种语法时可以使用该方式 。

猜你喜欢