JavaScript 事件冒泡和事件委托 JavaScript 事件冒泡和事件委托

一、事件冒泡

1.1 什么是事件冒泡?

事件冒泡 是一种 DOM 事件传播机制。当一个事件发生在某个元素上时,它会按照从最具体的目标元素到其祖先元素的顺序依次触发,直到到达 documentwindow(顶层元素)。这一机制使得子元素触发的事件可以“冒泡”到父元素上。

1.2 事件传播机制

事 当 DOM 中的某个事件(如点击)发生时,会按照以下三个阶段传播:

  1. 捕获阶段(Capture Phase)
    • 事件从顶层元素(windowdocument)向目标元素传播。
    • 此阶段主要用于捕获事件。
    • 默认情况下,捕获阶段不会触发事件处理程序。
  1. 目标阶段(Target Phase)
    • 事件到达目标元素,并触发绑定在目标元素上的事件处理程序。
  1. 冒泡阶段(Bubble Phase)
    • 事件从目标元素向上传播,依次经过其父元素、祖先元素,最终到达 documentwindow

1.3 事件冒泡的基本用法

1.3.1 如何使用事件冒泡

假设有如下的 HTML 结构:


 
 Click me
 

传统 JavaScript 示例

document.getElementById('grandparent').addEventListener('click', () => {
 console.log('Grandparent clicked');
});
document.getElementById('parent').addEventListener('click', () => {
 console.log('Parent clicked');
});
document.getElementById('child').addEventListener('click', () => {
 console.log('Child clicked');
});

当点击 button 时,输出:

Child clicked
Parent clicked
Grandparent clicked
  1. 事件先触发目标元素 button 的事件处理程序。
  2. 然后事件冒泡到其父元素 div#parent,触发其事件处理程序。
  3. 最后事件冒泡到祖先元素 div#grandparent,触发其事件处理程序。

上面事件传播的顺序是:目标阶段冒泡阶段

1.3.2 如何阻止事件冒泡

调用 event.stopPropagation() 可以阻止事件从目标元素冒泡到父元素。

document.getElementById('child').addEventListener('click', (event) => {
 event.stopPropagation(); // 阻止冒泡
 console.log('Child clicked');
});
document.getElementById('parent').addEventListener('click', () => {
 console.log('Parent clicked');
});

点击 button 时,只输出:

Child clicked

父元素和祖先元素的事件处理程序不会被触发。

1.4 事件捕获

1.4.1 如何监听捕获阶段事件

默认情况下,事件处理程序只会在 目标阶段冒泡阶段 触发。如果需要在捕获阶段监听事件,可以在绑定事件时将 addEventListener 的第三个参数设置为 true

document.getElementById('grandparent').addEventListener(
 'click',
 () => {
 console.log('Grandparent clicked during capture');
 },
 true // 捕获阶段监听
);

当点击 button 时,上面输出顺序变为:

Grandparent clicked during capture
Child clicked
Parent clicked
Grandparent clicked

捕获阶段的事件处理程序先于目标阶段和冒泡阶段执行。

1.4.2 事件捕获与冒泡的区别

特性

捕获阶段

冒泡阶段

传播方向

从顶层到目标元素

从目标元素到顶层

默认行为

默认不触发处理程序

默认触发绑定的事件处理程序

应用场景

捕获特殊情况(如全局监听)

事件委托、常见交互

1.5 应用场景

1.5.1 事件委托

事件委托 是事件冒泡最常见的应用场景,通过在父元素上绑定事件处理程序,可以监听所有子元素的事件,而不需要逐一为每个子元素绑定事件。

假设有一个动态列表,点击列表项时触发某些逻辑:


 Item 1
 Item 2

动态添加一个列表项:

document.getElementById('list').addEventListener('click', (event) => {
 //通过event.target判断具体子元素,从而执行相应的逻辑
 if (event.target.tagName === 'LI') {
 console.log('Clicked item:', event.target.textContent);
 }
});
// 动态添加列表项
const newItem = document.createElement('li');
newItem.textContent = 'Item 3';
document.getElementById('list').appendChild(newItem);

1.5.2 表单事件处理

对于表单中的多个输入框,可以利用事件冒泡将验证逻辑集中在父元素上,而不是为每个输入框单独绑定事件。


 
 
 Submit

集中处理输入验证:

document.getElementById('form').addEventListener('input', (event) => {
 if (event.target.name === 'username') {
 console.log('Validating username...');
 } else if (event.target.name === 'email') {
 console.log('Validating email...');
 }
});

1.5.2 模态框关闭

当需要点击模态框外部区域关闭模态框时,可以通过监听全局 document 的事件,并判断是否点击到了模态框外部。


 
 This is a modal.
 

关闭模态框的逻辑:

document.addEventListener('click', (event) => {
 const modal = document.getElementById('modal');
 const modalContent = document.getElementById('modal-content');
 if (event.target === modal & !modalContent.contains(event.target)) {
 modal.style.display = 'none';
 console.log('Modal closed');
 }
});

1.6 事件冒泡的优缺点

优点

  1. 减少事件绑定
  • 使用事件委托时,可以将事件处理程序绑定到父元素,而不是每个子元素,提高性能。
  1. 简化动态元素处理
  • 动态生成的子元素不需要单独绑定事件,父元素的事件处理程序即可捕获它们的事件。

缺点

  1. 意外触发
  • 如果不控制好事件冒泡,父元素可能被意外触发,导致逻辑错误。
  1. 调试复杂性
  • 嵌套事件和冒泡可能导致事件触发顺序难以理解,尤其在嵌套层次较深时。

二、事件委托

2.1 什么是事件委托?

事件委托 是一种事件处理模式,主要利用事件冒泡机制。通过将事件处理程序绑定到父元素,而不是直接绑定到每个子元素,父元素可以捕获并处理其子元素触发的事件。即使子元素是动态生成的,这种方法仍然有效。

2.2 事件委托的核心机制

  1. 事件冒泡机制
  • 当一个 DOM 元素上的事件被触发时,事件会从目标元素逐层向上冒泡到其祖先元素(直至 documentwindow)。
  • 父元素可以通过捕获冒泡事件并判断事件目标 (event.target) 来执行相应的操作。
  1. 集中管理
  • 通过将事件处理程序绑定到父元素,可以集中处理子元素的事件,而不需要单独为每个子元素绑定处理程序。

2.3 事件委托基本用法

假设我们有如下 HTML 结构:


 Item 1
 Item 2
 Item 3
  1. 传统事件绑定

为每个 li 绑定事件:

document.querySelectorAll('#list li').forEach((item) => {
 item.addEventListener('click', () => {
 console.log('Item clicked');
 });
});
  1. 使用事件委托

通过父元素 ul 捕获 li 的点击事件:

document.getElementById('list').addEventListener('click', (event) => {
 if (event.target.tagName === 'LI') {
 console.log('Item clicked:', event.target.textContent);
 }
});

无论 ul 内有多少个 li,只需给父元素 ul 绑定一次事件。父元素捕获到冒泡事件后,通过 event.target 判断事件是由哪个子元素触发的。

2.4 应用场景

因为事件委托依赖于事件冒泡机制,所以事件冒泡的场景即是事件委托场景

2.5 事件委托的优缺点

优点

  1. 减少事件绑定
  • 无需为每个子元素绑定事件处理程序,可以显著减少事件绑定的数量,从而提升性能。
  • 特别是在有大量子元素时,事件委托的效率更高。
  1. 动态元素支持
  • 新增的子元素会自动被父元素的事件处理程序捕获,无需重新绑定。
  1. 代码简洁
  • 集中管理事件逻辑,使代码更清晰、更易维护。

缺点

  1. 目标判断复杂
  • 父元素需要通过 event.target 判断事件的具体来源,当子元素的层次较多或结构复杂时,逻辑可能变得繁琐。
  1. 不支持不冒泡的事件
  • 某些事件(如 focusblur)不支持冒泡,无法使用事件委托。
  1. 父元素过多监听
  • 如果父元素有大量事件监听逻辑,可能会影响性能。

三、事件冒泡和事件委托的关系

事件委托 依赖 于事件冒泡的机制

  1. 冒泡是实现委托的基础
    事件冒泡将子元素的事件传播到父元素,父元素可以通过监听这些事件并通过 event.target 确定具体的触发子元素,从而处理这些事件。
  2. 委托利用了冒泡机制的特点
    事件委托通过将事件处理程序绑定到父级或更高层级的元素,实现了对子元素事件的集中处理。
  3. 委托的优势得益于冒泡
  • 减少事件绑定数量,提升性能。
  • 父级元素可动态处理新增或删除的子元素的事件。

作者:有你的星空原文地址:https://blog.csdn.net/ltlt654321/article/details/144106728

%s 个评论

要回复文章请先登录注册