事件监听器移除方法的更多选择

本文译者为 360 奇舞团前端开发工程师 - 赵少勇

原文标题:You've Got Options for Removing Event Listeners

原文作者:Alex MacArthur

原文地址:www.macarthur.me/posts/optio...
回顾JavaScript中移除事件监听器的常见方法。

在运行时清理代码是构建高效、可预测应用程序的必不可少的部分。在JavaScript中,合理管理事件监听器,在不再需要监听事件的时候将它们移除是必要的。

有几种方法可实现上述操作,每种方法都有其自身的权衡,使其在特定情况下更加合适。我们将介绍一些经常使用的方法,以及在选择何种方法时要考虑的一些因素。

我们将用下面代码进行实验 :

一个带有单个click事件监听器的按钮:

javascript 复制代码
<button id="button">Do Something</button>

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

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

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

1.使用 removeEventListener

这可能是最常用的方法,但也最有可能让你疲惫不堪。removeEventListener 方法接受三个参数:要移除的监听器类型、该监听器的回调函数以及一个选项对象。

但是棘手之处在于:这些参数必须和设置监听器时使用的参数完全匹配,包括相同的回调引用。否则removeEventListener() 将不起作用。

鉴于此,以下操作将是完全无效的:

javascript 复制代码
document.getElementById('button').addEventListener('click', () => {
  console.log('clicked!');
});

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

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

javascript 复制代码
const myCallback = () => {
  console.log('clicked!');
};

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

或者,对于特定的场景,您还可以使用在函数内部使用函数名来删除监听器:

javascript 复制代码
document
  .getElementById('button')
  .addEventListener('click', function myCallback() {
    console.log('clicked!');

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

尽管有这些限制,.removeEventListener() 的优点是代码语义明确。

2.使用addEventListener的once选项

addEventListener() 方法的第二个对象参数有一个选项:once 选项,可以用来设置在仅打算使用一次的情况下自行清除事件绑定。它的使用和听起来一样简单,如果将其设置为 true,则监听器在第一次被调用后会自动被移除:

javascript 复制代码
const button = document.getElementById('button');

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

// 'clicked!'
button.click();

// No more listeners!
getEventListeners(button) // {}

如果适用于您的使用场景,即使使用匿名函数,这种方法也是有效的,你的监听器只会被调用一次。

克隆和替换节点

有时,你可能不知道给定节点上的所有活动监听器,但你想要将它们全部移除。在这种情况下,可以克隆整个节点并用该克隆节点替换自身。使用 .cloneNode() 方法,通过 .addEventListener() 附加的任何监听器都不会被保留,从而获得一个干净的节点。

在客户端JavaScript的石器时代,您会看到通过查询父节点,并替换特定子节点以进行此操作:

javascript 复制代码
button.parentNode.replaceChild(button.cloneNode(true), button);

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

javascript 复制代码
button.replaceWith(button.cloneNode(true));

这里唯一可能让你困扰的是,行内监听器会被保留,这意味着 onclick 事件仍会被触发:

html 复制代码
<button id="button" onclick="console.log('clicked!')">
  Do Something
</button>

总的来说,如果你需要删除任何类型的监听器,这是一个不错的选择。它的缺点是目的不明显,有些讨巧。

使用AbortController()

如果你和我一样,可能只听说过 AbortController 用于 取消 fetch() 请求,实际上它的使用场景更丰富。

.addEventListener() 可以配置一个信号,用于命令式地中止/删除监听器。当相应的控制器调用 .abort() 时,监听器会被移除:

javascript 复制代码
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() 的潜在陷阱的情况下移除监听器。还有一个更大的优势:您可以使用一个信号一次性删除多个监听器,使用匿名函数也可以:

javascript 复制代码
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()
  • 如果只需要触发一次回调,请在 .addEventListener() 中使用 once 选项。
  • 如果需要在一次操作中无差别地删除所有的监听器,请使用克隆替换方法。
  • 如果有一系列监听器希望命令式地一次性删除,请使用 AbortController()
相关推荐
不简说1 小时前
# JS 代码技巧 vol.7 — 20 个浏览器 API 实战,自带 API 能干的事别自己封装
前端·javascript·面试
Hilaku1 小时前
为什么 Cloudflare 能做到毫秒级冷启动,而其它云厂商却不行?
前端·javascript·程序员
2301_794461571 小时前
JavaScript 基础知识点详解
前端·javascript·html
早期的虫儿有鸟吃2 小时前
vue2--Vuex 模块化
开发语言·前端·javascript
Cobyte2 小时前
使用 JavaScript 实现有限状态机的经典问题
前端·javascript·vue.js
acheding2 小时前
File System Access API 实战:让网页真正读写本地文件
前端·javascript·vue.js·编辑器·markdown
何时梦醒2 小时前
⚛️ React 19 + TypeScript 深度学习笔记 —— 从组件化思维到 WebGPU 端侧 AI 落地
前端·javascript·人工智能
用户938515635073 小时前
从 Vite 脚手架到 WebGPU 推理:手写一个 DeepSeek-R1 浏览器端大模型 Demo
javascript·人工智能·全栈
沉迷学习 日益消瘦3 小时前
7 React Native / Expo 开发体验:项目结构、跨平台适配与构建发布
javascript·react native·react.js
acheding3 小时前
把 CodeMirror 6 调教成 Markdown 编辑器:扩展、装饰与门面
javascript·vue.js·编辑器·markdown