
文档教程前端【免费下载链接】zh.javascript.info现代 JavaScript 教程The Modern JavaScript Tutorial以最新的 ECMAScript 规范为基准通过简单但足够详细的内容为你讲解从基础到高阶的 JavaScript 相关知识。项目地址https://gitcode.com/gh_mirrors/zh/zh.javascript.info点击查看免费下载本文基于《现代 JavaScript 教程》zh.javascript.info「浏览器事件简介」章节中的编程任务「哪个处理程序会运行」展开。文章完整还原题目与官方解答并结合章节正文与源码深入讲解removeEventListener为什么要求「完全相同的函数对象」、匿名函数为何无法被移除以及addEventListener与button.onclick这两套处理程序体系是如何互不干扰地共存的。读完本文你将掌握事件处理程序的可移除性规则并能写出正确的添加—移除配对代码。一、题目回顾三种处理程序操作混在同一段代码里本任务位于仓库 2-ui/2-events/01-introduction-browser-events/03-which-handlers-run/ 目录下对应的题目文件内容如下在变量中有一个按钮。它上面没有处理程序。执行以下代码之后哪些处理程序会在按钮被点击时运行会显示哪些 alertbutton.addEventListener(click, () alert(1)); button.removeEventListener(click, () alert(1)); button.onclick () alert(2);这道题的「陷阱」在于三行代码分别使用了addEventListener、removeEventListener和 DOM 属性onclick三种不同的处理程序操作方式而它们各自的语义并不相同。官方解答给出的答案是答案1和2。也就是说点击按钮时两个 alert 都会弹出先是1然后是2。下面我们从三个层面拆解为什么。二、第一层为什么匿名函数的 removeEventListener 是无效操作题目第二行代码button.removeEventListener(click, () alert(1));表面上它试图移除第一行addEventListener注册的处理程序但实际上移除操作完全没有生效。原因在于removeEventListener要求传入的必须是「被添加时那个完全相同的函数对象」而这里传入的是一个新的匿名箭头函数——虽然它的代码和第一个箭头函数一模一样但它们是两个不同的函数对象。官方解答对此的解释是要移除处理程序我们需要传递正确的所分配的函数。在代码中传递了一个新的函数该函数看起来相同但仍然是另一个函数。这一规则在章节正文 article.md 中有更详细的警告框说明要移除处理程序我们需要传入与分配的函数完全相同的函数。 这不起作用 js no-beautify elem.addEventListener( click , () alert(Thanks!)); // .... elem.removeEventListener( click, () alert(Thanks!)); 处理程序不会被移除因为 removeEventListener 获取了另一个函数 —— 使用相同的代码但这并不起作用因为它是一个不同的函数对象。用语言描述就是addEventListener和removeEventListener在浏览器内部都是**按函数对象的身份引用**进行匹配的而不是按代码文本匹配。两个各自创建的箭头函数字面量即使源码完全相同也是堆上两个不同的对象身份不同自然无法命中。三、第二层如何正确移除——存储函数引用要移除一个由addEventListener添加的处理程序唯一可靠的办法是在添加时把函数存进变量或用具名函数声明移除时传入同一个引用。官方解答给出了标准写法function handler() { alert(1); } button.addEventListener(click, handler); button.removeEventListener(click, handler);这里handler是同一个函数对象addEventListener注册的是它removeEventListener移除的也是它因此移除成功点击按钮时不会再触发任何由addEventListener注册的click处理程序。章节正文还强调了一个容易被忽略的事实请注意 —— 如果我们不将函数存储在一个变量中那么我们就无法移除它。由addEventListener分配的处理程序将无法被读回。也就是说处理程序一旦以匿名函数的形式传给addEventListener就再没有任何 API 能把它查出来再移除——浏览器不会提供类似getEventListeners的读取接口在常规 Web 环境下你唯一能做的就是在添加时就保存好引用。这是编写可清理代码时必须遵守的纪律凡是可能被移除的处理程序一律用具名函数或变量引用。3.1 常见的错误示范对比写法能否成功移除原因addEventListener(click, () alert(1))后removeEventListener(click, () alert(1))不能两个箭头函数是不同的函数对象addEventListener(click, handler)后removeEventListener(click, handler)能同一个函数对象被两次传入addEventListener(click, handler)后removeEventListener(click, handler.bind(...))等变体不能bind会生成新函数对象同样不匹配四、第三层addEventListener 与 onclick 互不覆盖题目第三行代码button.onclick () alert(2);onclick是 DOM 属性property它是独立于addEventListener体系之外的另一套处理程序分配机制。因此无论addEventListener是否成功注册了处理程序、无论它注册了几个button.onclick的赋值都会独立生效。官方解答最后一句点明了这一点无论addEventListener怎样button.onclick处理程序都会触发。这也是为什么最终答案是1和2两个 alert 都会弹出alert(1)—— 由addEventListener注册removeEventListener 未命中故保留alert(2)—— 由button.onclick赋值与前者互不影响。4.1 两种体系的根本差异章节正文对这两套机制的区别讲得很清楚DOM 属性property方式elem.onclick function。一个事件只能有一个处理程序新赋值会覆盖旧赋值要移除就用elem.onclick null。它是大小写敏感的必须写成elem.onclick而非elem.ONCLICK。方法method方式elem.addEventListener(event, handler[, options])/elem.removeEventListener(event, handler[, options])。同一个事件可以注册多个处理程序全部按添加顺序触发互不覆盖。正文还演示了两者可以同时使用input idelem typebutton valueClick me/ script function handler1() { alert(Thanks!); }; function handler2() { alert(Thanks again!); } elem.onclick () alert(Hello); elem.addEventListener(click, handler1); // Thanks! elem.addEventListener(click, handler2); // Thanks again! /script点击后三个 alert 都会依次弹出先是Hello来自onclick属性然后是Thanks!、Thanks again!来自两个addEventListener注册的处理程序。这正是本题场景的放大版addEventListener的处理程序和onclick属性处理程序是两条独立轨道互不覆盖、都会执行。不过正文也给出了务实建议通常我们只使用其中一种方式混用反而会让代码行为难以预测。五、addEventListener 的 options 参数与相关细节既然本题的核心是addEventListener这里顺带补齐它的完整签名与可选参数详见 article.mdelement.addEventListener(event, handler[, options]);event事件名例如click。handler处理程序函数或带handleEvent方法的对象。options附加可选对象常用属性包括once为true时处理程序在被触发一次后会自动删除自动移除capture事件处理阶段捕获/冒泡由于历史原因也可以是布尔值false/true等价于{capture: false/true}passive为true时处理程序不会调用preventDefault()。对应地移除语法为element.removeEventListener(event, handler[, options]);注意removeEventListener的options也要与添加时保持一致尤其涉及capture阶段时否则同样可能匹配失败。另外addEventListener还支持对象处理程序传入一个带有handleEvent(event)方法的对象事件发生时浏览器会调用obj.handleEvent(event)也可以用同一个对象监听多个事件类型。正文以Menu类为例展示了这种用法——处理程序不再是无名函数代码更易组织与维护。六、拓展哪些事件只能用 addEventListener与本题相关的另一个知识点是并非所有事件都能通过 DOM 属性赋值。例如DOMContentLoaded事件就没有对应的onDOMContentLoaded属性可用// 永远不会运行 document.onDOMContentLoaded function() { alert(DOM built); }; // 这种方式可以运行 document.addEventListener(DOMContentLoaded, function() { alert(DOM built); });这解释了为什么正文总结中把addEventListener称为最灵活的方式——它是唯一能覆盖全部事件的通用注册接口。七、总结本题背后的三条核心规则回到题目本身把三段代码拆开看addEventListener(click, () alert(1))成功注册了一个处理程序removeEventListener(click, () alert(1))因传入的是全新函数对象而匹配失败处理程序仍然保留button.onclick () alert(2)走 DOM 属性轨道独立生效不会覆盖也无需关心addEventListener的状态。因此点击按钮时依次弹出1和2。本题浓缩了三条值得牢记的事件处理规则它们全部可以在仓库正文 2-ui/2-events/01-introduction-browser-events/article.md 与官方解答中找到依据移除必须传同一个函数对象removeEventListener按函数身份匹配匿名函数看上去一样也救不了它addEventListener 与 onclick 是两条独立轨道DOM 属性property同一事件只能有一个处理程序、可被覆盖方法体系支持多个处理程序共存两者互不覆盖可移除性是代码设计问题需要动态增删处理程序时务必保存函数引用或使用具名函数否则注册后便无法移除可能造成重复触发或内存占用问题。如果你希望继续深入这个章节的完整知识体系包括this指向、事件对象、handleEvent等可以直接阅读仓库中的浏览器事件简介想验证本节所学可以继续完成同目录下的其他任务例如点击隐藏按钮与小球移动。赞分享文档教程前端【免费下载链接】zh.javascript.info现代 JavaScript 教程The Modern JavaScript Tutorial以最新的 ECMAScript 规范为基准通过简单但足够详细的内容为你讲解从基础到高阶的 JavaScript 相关知识。项目地址https://gitcode.com/gh_mirrors/zh/zh.javascript.info点击查看免费下载相关推荐现代 JavaScript 教程对象方法、this 与箭头函数的运行时绑定机制现代 JavaScript 教程对象方法、this 与箭头函数的运行时绑定机制 对象是 JavaScript 中组织数据的基本单位而 方法 method文档教程前端Claudian 插件实战5 步装好 Obsidian AI 助手让 AI 帮你整理知识库Claudian 插件实战5 步装好 Obsidian AI 助手让 AI 帮你整理知识库 整理一堆零散笔记时最耗时间的就是把它们重新归位。装上 ClauAI 应用代码智能体交互助手人工智能AI Agent现代 JavaScript 教程深入理解 JavaScript 对象Object核心机制现代 JavaScript 教程深入理解 JavaScript 对象Object核心机制 对象Object是 JavaScript 中最重要的数据类型文档/教程前端上一篇Nitrogen开发环境搭建Windows、Linux与FreeBSD系统的安装教程下一篇VueUse Motion 在 Nuxt 项目中的使用指南创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考