如何移除事件侦听器?

大家好,这里是大家的林语冰。本期《前端翻译计划》共享的是复盘在 JS 中移除事件侦听器的若干最常见方法。

免责声明

本文属于是语冰的直男翻译了属于是,略有删改,仅供粉丝参考,英文原味版请传送 You’ve Got Options for Removing Event Listeners

在运行时清理代码是构建高效、可预测的 App 不可或缺的一部分。JS 中实现的方法之一是妥善地管理事件侦听器 —— 具体而言,就是在不需要它们时将其移除。

有一大坨方法可以实现,每种方法都有自己因地制宜的权衡。我们将介绍若干最常用的策略,以及当您在任何特定时机试图决定最佳策略时,需要牢记的某些注意事项。

我们将修改下述设置 —— 一个附加了单个 click 事件侦听器的按钮:

<button id="button">Do Something</button>

<script>
  document.getElementById('button').addEventListener('click', () => {
    console.log('clicked!')
  })
</script>

使用 Chrome 的 getEventListeners() 函数,您能且仅能看到一个附加到该元素的侦听器:

如何移除事件侦听器?

如果您需要移除该侦听器,您可以使用下述方法。

使用 .removeEventListener()

这可能是最显而易见、但也最有可能让你头皮发麻的方案。.removeEventListener() 方法接受 3 个参数:

  1. 要移除的侦听器类型
  2. 该侦听器的回调函数
  3. 一个选项对象

但此处(可能)存在棘手的部分:这些确切的参数必须与设置侦听器时使用的参数完全匹配,包括对内存中回调的相同引用。否则,.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)

或者,对于特定用例,您还可以通过从函数本身引用伪匿名(pseudo-anonymous)函数来移除侦听器:

document
  .getElementById('button')
  .addEventListener('click', function myCallback() {
    console.log('clicked!')

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

尽管存在特殊性,.removeEventListener() 的优点是其目的不言而喻。当您阅读代码时,您对其功能心照不宣。

使用 .addEventListener()once 选项

.addEventListener() 方法附带了一个工具:once 选项,如果是一次性使用,就可以用它自我清理。如果 once 设置为 true,侦听器会在首次调用后自动删除自身:

const button = document.getElementById('button')

button.addEventListener(
  'click',
  () => {
    console.log('clicked!')
  },
  { once: true }
)

// 'clicked!'
button.click()

// 侦听器已移除!
getEventListeners(button) // {}

假设它适合您的用例,如果您热衷于使用匿名函数,那么此方法可能恰到好处,因为您的侦听器只需调用一次。

克隆/替换节点

有时,您不知道给定节点上所有激活的侦听器,但您明确想要消灭它们。在这种情况下,可以克隆整个节点并用该克隆替换自身。使用 .cloneNode() 方法,通过 .addEventListener() 连接的侦听器都不会被保留。

回到客户端 JS 的石器时代,您会看到通过查询父节点,并用克隆替换特定子节点来完成此操作:

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

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

button.replaceWith(button.cloneNode(true))

可能会让您进退维谷的一件事是保留了内部侦听器,这意味着,具有 onclick 属性的按钮仍会按定义触发:

<button id="button" onclick="console.log('clicked!')">Do Something</button>

总而言之,如果您需要用无差别地暴力移除任意类型的侦听器,那么这值得一试。虽然但是,它的缺点是其目的不太直观。有些人甚至可能称其为奇技淫巧。

使用 AbortController()

此方案于我而言乃知识盲区。如果您像我一样,您可能只听说过 AbortController 可用于取消 fetch() 请求。但它显然比这更灵活。

截至最近,.addEventListener() 可以配置有 signal 来强制中止/移除侦听器。当相应的控制器调用 .abort() 时,该 signal 会触发移除侦听器:

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

button.addEventListener('click', () => console.log('clicked!'), { signal })

// 移除侦听器!
controller.abort()

最明显的优势可能是人体工程学。(在我看来)这是一种更清晰的移除侦听器的方法,而不会出现 .removeEventListener() 的潜在问题。但还有一个更具战略意义的优势:您可以使用一个 signal 一次性移除任意类型的多个监听器。使用匿名函数也问题不大:

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 })

// 一次性移除所有侦听器:
controller.abort()

我唯一犹豫不决的原因是浏览器的支持。这是一项相对较新的功能,自 2021 年(v90)起 Chrome 才提供全面支持。因此,如果您需要支持几年前的浏览器版本,请牢记这一点。

我该如何选择?

总而言之,实事求是即可:

  • 如果回调函数已赋值给变量,且可以轻松访问添加侦听器的位置,请使用 .removeEventListener()
  • 如果您只需触发一次回调,请使用 .addEventListener() 中的 once 选项。
  • 如果您需要无差别地消灭多个侦听器,请使用克隆和替换方法。
  • 如果您想立即强制移除一系列侦听器,或者您只是喜欢该语法,请使用 AbortController()

您现在收看的是《前端翻译计划》,学废了的小伙伴可以订阅此专栏合集,我们每天佛系投稿,欢迎持续关注前端生态。谢谢大家的点赞,掰掰~

如何移除事件侦听器?

原文链接:https://juejin.cn/post/7315846805920579593 作者:人猫神话

(0)
上一篇 2023年12月24日 下午4:42
下一篇 2023年12月24日 下午4:52

相关推荐

发表回复

登录后才能评论