前言
在项目不断扩大之时,管理用户交互变的越来越重要,为每个交互元素附加一个事件监听器是一种糟糕的做法,因为它会导致代码混乱、内存消耗增加以及性能瓶颈。这时,事件委托就派上用场了。
认识dom事件传播
三个阶段
当事件在 DOM 元素上触发时,它不会简单地到达目标并停止。相反,它会经历以下阶段:
- 捕获阶段 旅程从
window
级别开始,沿着 DOM 树向下移动,经过每个祖先元素,直到到达目标的父级。带有(中的第三个参数)的事件监听器在此触发useCapture = true``addEventListener
- 目标阶段在此阶段,事件到达预期的目标元素。所有直接附加到此元素的监听器都会被触发
- 冒泡阶段 命中目标后,事件会沿着 DOM 向上"冒泡",从目标的父元素到祖父元素,依此类推,直到到达目标
window
。默认情况下,大多数事件监听器都在此阶段运行
事件在dom树中流动过程
ini
< div id = "grandparent" >
< div id = "parent" >
< button id = "child" >点击我</ button >
</ div >
</ div >
如果您单击,则事件流程如下:<button id="child">
click
- 捕获 -
window
->document
-><html>
-><body>
-><div id="grandparent">
-><div id="parent">
- 目标 -
<button id="child">
- 冒泡 -
<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
-- 当指针离开元素时触发
为什么它们不起泡
由于浏览器的工作方式以及过去的兼容性问题,通常无法触发此类事件。focus
和blur
旨在在获得或失去焦点的特定元素上触发,因此不存在冒泡。mouseenter
和mouseleave
与mouseover
和 mouseout
配对(它们会产生冒泡);但是与mouseover
和 mouseout
不同,mouseenter
和 mouseleave
仅在指针位于元素上(而不是其子元素上)时触发。
对于非冒泡事件只能通过自定义冒泡事件来替代
总结
事件委托通过将单个监听器附加到父元素来简化事件处理。当子元素触发事件时,它会向上冒泡到父元素,从而减少内存占用并简化代码。
这种技术在管理大量相似元素(例如列表项或按钮)时非常有效,尤其是在它们动态生成的情况下。父级监听器无需额外配置即可处理新添加元素的事件。
并非所有事件都会冒泡 focus
、blur
、mouseleave
等是例外。对于这些事件,可以用 focusin
、focusout
或自定义冒泡事件等替代方法。