事件委托的深层逻辑:当冒泡不够时⁉️

前言

在项目不断扩大之时,管理用户交互变的越来越重要,为每个交互元素附加一个事件监听器是一种糟糕的做法,因为它会导致代码混乱、内存消耗增加以及性能瓶颈。这时,事件委托就派上用场了。

认识dom事件传播

三个阶段

当事件在 DOM 元素上触发时,它不会简单地到达目标并停止。相反,它会经历以下阶段:

  1. 捕获阶段 旅程从window级别开始,沿着 DOM 树向下移动,经过每个祖先元素,直到到达目标的父级。带有(中的第三个参数)的事件监听器在此触发useCapture = true``addEventListener
  2. 目标阶段在此阶段,事件到达预期的目标元素。所有直接附加到此元素的监听器都会被触发
  3. 冒泡阶段 命中目标后,事件会沿着 DOM 向上"冒泡",从目标的父元素到祖父元素,依此类推,直到到达目标window。默认情况下,大多数事件监听器都在此阶段运行

事件在dom树中流动过程

ini 复制代码
< div id = "grandparent" >
< div id = "parent" >
< button id = "child" >点击我</ button > 
</ div > 
</ div > 

如果您单击,则事件流程如下:<button id="child"> click

  1. 捕获 -window -> document -> <html> -> <body> -> <div id="grandparent"> -> <div id="parent">
  2. 目标 - <button id="child">
  3. 冒泡 - <button id="child"> -> <div id="parent"> -> <div id="grandparent"> -> <body> -> <html> ->document -> window

什么是事件委托

事件委托是一种将事件监听器添加到多个子元素的父元素上,而不是分别添加到每个子元素上的方法。当子元素上发生事件时,它会触发父元素上的监听器,父元素会检查是哪个子元素触发了该事件。

假设一个<ul>包含<li>以下项目的简单列表:

css 复制代码
< ul id = "myList" > 
< li >项目 1 </ li > 
< li >项目 2 </ li >
< li >项目 3 </ li >
< li >项目 4 </ li > 
</ ul > 
  

而不是为每个添加一个点击监听器<li>

ini 复制代码
const listItems = document . querySelectorAll ( '#myList li' ); 
listItems . forEach ( item => { 
  item . addEventListener ( 'click' , ( event ) => { console . log ( `点击于: $ { event . target . textContent } ` );
  });
 });           

通过事件委托,你可以将一个监听器附加到<ul>父级:

ini 复制代码
onst myList =文档. getElementById ( 'myList' ); 

myList . addEventListener ( 'click' , ( event ) => { 
// 检查点击的元素是否为 <li>
if ( event . target . tagName === 'LI' ) {
console . log ( ` Clicked on : $ { event . target . textContent } ` );
} 
});   
  

在此示例中,当<li>点击任意一个时,click事件都会冒泡到。然后,myList上的单个事件监听器会检查是否是触发了该事件,并采取相应的措施:myList``event.target.tagName``<li>

为什么事件委托如此重要

  • 无需添加数百或数千个监听器,只需几个父容器就足够了,从而大大减少内存占用
  • 更少的监听器可以提高浏览器整体系统内存的使用率,并减少 JavaScript 引擎在事件管理和调度方面的工作量
  • 它支持动态创建元素,这非常实用。假设在页面加载后(例如,在 API 调用后)<li>添加了新元素,监听器仍然有效。无需重新连接监听器。#myList``#myList

事件委托中常见的误区

csharp 复制代码
event.target vs event.currentTarget
  • event.target 是触发事件的特定元素。
  • event.currentTarget 是事件监听器实际附加到的元素。
scss 复制代码
stopPropagation ()和stopImmediatePropagation () 
  • event.stopPropagation() -- 此方法仅允许事件停止沿 DOM 树向上或向下冒泡或捕获。如果在子元素的事件处理程序中执行此方法,则其祖先元素上的任何委托监听器都将无法访问该事件
  • event.stopImmediatePropagation() 这不stopPropagation()的复制粘贴。它的相似之处仅限于添加了这个效果:它阻止进一步的事件传播,并阻止绑定到同一元素的任何其他监听器被执行。

在某些情况下,它们会破坏委托处理程序,例如:子元素的事件处理程序调用stopPropagation将导致位于 DOM 层次结构中更高层级的任何委托监听器的功能失效。委托监听器将无法接收事件。这对于分析、集中式 UI 逻辑或可访问的自定义控件功能尤其麻烦。

非冒泡事件

最突出的非冒泡事件包括:

  • focus-- 当元素获得焦点时触发
  • blur-- 当元素失去焦点时触发
  • mouseenter-- 当指针进入元素时触发
  • mouseleave-- 当指针离开元素时触发

为什么它们不起泡

由于浏览器的工作方式以及过去的兼容性问题,通常无法触发此类事件。focusblur旨在在获得或失去焦点的特定元素上触发,因此不存在冒泡。mouseentermouseleavemouseovermouseout配对(它们会产生冒泡);但是与mouseovermouseout不同,mouseentermouseleave仅在指针位于元素上(而不是其子元素上)时触发。

对于非冒泡事件只能通过自定义冒泡事件来替代

总结

事件委托通过将单个监听器附加到父元素来简化事件处理。当子元素触发事件时,它会向上冒泡到父元素,从而减少内存占用并简化代码。

这种技术在管理大量相似元素(例如列表项或按钮)时非常有效,尤其是在它们动态生成的情况下。父级监听器无需额外配置即可处理新添加元素的事件。

并非所有事件都会冒泡 focusblurmouseleave 等是例外。对于这些事件,可以用 focusinfocusout 或自定义冒泡事件等替代方法。

相关推荐
雲墨款哥19 分钟前
JS算法练习-Day10-判断单调数列
前端·javascript·算法
JuneXcy24 分钟前
11.web api 2
前端·javascript·html
zYear29 分钟前
Elpis 全栈应用框架-- 总结
前端·javascript
程序员曦曦31 分钟前
10:00开始面试,10:06就出来了,问的问题有点变态。。。
自动化测试·软件测试·功能测试·程序人生·面试·职场和发展
Juchecar1 小时前
分析:将现代开源浏览器的JavaScript引擎更换为Python的可行性与操作
前端·javascript·python
前端小巷子3 小时前
Vue 事件绑定机制
前端·vue.js·面试
是乐谷3 小时前
阿里云杭州 AI 产品法务岗位信息分享(2025 年 8 月)
java·人工智能·阿里云·面试·职场和发展·机器人·云计算
uhakadotcom3 小时前
开源:subdomainpy快速高效的 Python 子域名检测工具
前端·后端·面试
伍哥的传说3 小时前
Vue 3.5重磅更新:响应式Props解构,让组件开发更简洁高效
前端·javascript·vue.js·defineprops·vue 3.5·响应式props解构·vue.js新特性
德育处主任3 小时前
p5.js 3D 形状 "预制工厂"——buildGeometry ()
前端·javascript·canvas