当前位置: 首页 > ds >正文

【JavaScript-Day 42】深入解析事件冒泡与捕获:掌握事件委托的精髓

Langchain系列文章目录

01-玩转LangChain:从模型调用到Prompt模板与输出解析的完整指南
02-玩转 LangChain Memory 模块:四种记忆类型详解及应用场景全覆盖
03-全面掌握 LangChain:从核心链条构建到动态任务分配的实战指南
04-玩转 LangChain:从文档加载到高效问答系统构建的全程实战
05-玩转 LangChain:深度评估问答系统的三种高效方法(示例生成、手动评估与LLM辅助评估)
06-从 0 到 1 掌握 LangChain Agents:自定义工具 + LLM 打造智能工作流!
07-【深度解析】从GPT-1到GPT-4:ChatGPT背后的核心原理全揭秘
08-【万字长文】MCP深度解析:打通AI与世界的“USB-C”,模型上下文协议原理、实践与未来

Python系列文章目录

PyTorch系列文章目录

机器学习系列文章目录

深度学习系列文章目录

Java系列文章目录

JavaScript系列文章目录

01-【JavaScript-Day 1】从零开始:全面了解 JavaScript 是什么、为什么学以及它与 Java 的区别
02-【JavaScript-Day 2】开启 JS 之旅:从浏览器控制台到 <script> 标签的 Hello World 实践
03-【JavaScript-Day 3】掌握JS语法规则:语句、分号、注释与大小写敏感详解
04-【JavaScript-Day 4】var 完全指南:掌握变量声明、作用域及提升
05-【JavaScript-Day 5】告别 var 陷阱:深入理解 letconst 的妙用
06-【JavaScript-Day 6】从零到精通:JavaScript 原始类型 String, Number, Boolean, Null, Undefined, Symbol, BigInt 详解
07-【JavaScript-Day 7】全面解析 Number 与 String:JS 数据核心操作指南
08-【JavaScript-Day 8】告别混淆:一文彻底搞懂 JavaScript 的 Boolean、null 和 undefined
09-【JavaScript-Day 9】从基础到进阶:掌握 JavaScript 核心运算符之算术与赋值篇
10-【JavaScript-Day 10】掌握代码决策核心:详解比较、逻辑与三元运算符
11-【JavaScript-Day 11】避坑指南!深入理解JavaScript隐式和显式类型转换
12-【JavaScript-Day 12】掌握程序流程:深入解析 if…else 条件语句
13-【JavaScript-Day 13】告别冗长if-else:精通switch语句,让代码清爽高效!
14-【JavaScript-Day 14】玩转 for 循环:从基础语法到遍历数组实战
15-【JavaScript-Day 15】深入解析 while 与 do…while 循环:满足条件的重复执行
16-【JavaScript-Day 16】函数探秘:代码复用的基石——声明、表达式与调用详解
17-【JavaScript-Day 17】函数的核心出口:深入解析 return 语句的奥秘
18-【JavaScript-Day 18】揭秘变量的“隐形边界”:深入理解全局与函数作用域
19-【JavaScript-Day 19】深入理解 JavaScript 作用域:块级、词法及 Hoisting 机制
20-【JavaScript-Day 20】揭秘函数的“记忆”:深入浅出理解闭包(Closure)
21-【JavaScript-Day 21】闭包实战:从模块化到内存管理,高级技巧全解析
22-【JavaScript-Day 22】告别 function 关键字?ES6 箭头函数 (=>) 深度解析
23-【JavaScript-Day 23】告别繁琐的参数处理:玩转 ES6 默认参数与剩余参数
24-【JavaScript-Day 24】从零到一,精通 JavaScript 对象:创建、访问与操作
25-【JavaScript-Day 25】深入探索:使用 for...in 循环遍历 JavaScript 对象属性
26-【JavaScript-Day 26】零基础掌握JavaScript数组:轻松理解创建、索引、长度和多维结构
27-【JavaScript-Day 27】玩转数组:push, pop, slice, splice 等方法详解与实战
28-【JavaScript-Day 28】告别繁琐循环:forEach, map, filter 数组遍历三剑客详解
29-【JavaScript-Day 29】数组迭代进阶:掌握 reduce、find、some 等高阶遍历方法
30-【JavaScript-Day 30】ES6新特性:Set与Map,让你的数据管理更高效!
31-【JavaScript-Day 31】对象的“蓝图”详解:构造函数、newinstanceof 完全指南
32-【JavaScript-Day 32】深入理解 prototype、__proto__ 与原型链的奥秘
33-【JavaScript-Day 33】深入浅出 ES6 Class:从入门到精通面向对象新姿势
34-【JavaScript-Day 34】前后端数据交互的通用语:深入解析JSON
35-【JavaScript-Day 35】从 window 到 location,一文掌握浏览器对象模型 BOM
36-【JavaScript-Day 36】前端基石:深入理解 DOM 并精通五大元素选择器
37-【JavaScript-Day 37】在 DOM 树中“行走”:节点遍历
38-【JavaScript-Day 38】JS操控网页外观:从innerHTML到classList的全方位指南
39-【JavaScript-Day 39】从零到一,动态构建交互式网页的 DOM 节点操作秘籍
40-【JavaScript-Day 40】响应用户操作:事件监听与处理从入门到精通
41-【JavaScript-Day 41】JS 事件大全:click, keydown, submit, load 等常见事件详解与实战
42-【JavaScript-Day 42】深入解析事件冒泡与捕获:掌握事件委托的精髓


文章目录

  • Langchain系列文章目录
  • Python系列文章目录
  • PyTorch系列文章目录
  • 机器学习系列文章目录
  • 深度学习系列文章目录
  • Java系列文章目录
  • JavaScript系列文章目录
  • 前言
  • 一、什么是事件流?
    • 1.1 DOM 事件流的三个阶段
      • 1.1.1 事件捕获(Capturing Phase)
      • 1.1.2 目标阶段(Target Phase)
      • 1.1.3 事件冒泡(Bubbling Phase)
    • 1.2 可视化事件流
  • 二、控制事件流:addEventListener 的秘密
    • 2.1 第三个参数 `useCapture`
      • 2.1.1 `useCapture = false` (默认)
        • (1)代码示例
      • 2.1.2 `useCapture = true`
        • (1)代码示例
  • 三、管理事件传播:阻止与委托
    • 3.1 阻止事件传播:`event.stopPropagation()`
      • 3.1.1 为什么需要阻止传播?
      • 3.1.2 如何使用 `stopPropagation()`
        • (1)代码示例
    • 3.2 事件委托(Event Delegation)
      • 3.2.1 什么是事件委托?
      • 3.2.2 事件委托的实现原理
      • 3.2.3 代码实例:动态列表的点击事件
      • 3.2.4 事件委托的优势
        • (1) 性能提升
        • (2) 动态元素支持
  • 四、常见问题与注意事项
    • 4.1 `event.target` vs `event.currentTarget`
    • 4.2 `stopPropagation()` vs `preventDefault()`
  • 五、总结


前言

你好,未来的 JavaScript 大师!在前两篇文章中,我们学习了如何使用 addEventListener 监听用户操作,并了解了各种常见的网页事件。然而,当你在一个元素上触发一个事件(比如点击)时,事情真的就这么简单地结束了吗?实际上,这个事件会在 DOM 树中进行一次“旅行”。这次旅行的路径和规则,就是我们今天要探讨的核心——事件流(Event Flow)。理解事件流不仅是深入掌握 JavaScript 事件机制的关键,更是通往高效编程模式——事件委托(Event Delegation) 的必经之路。准备好了吗?让我们一起揭开事件传播的神秘面纱!


一、什么是事件流?

事件流描述的是从页面中接收事件的顺序。当你在一个 DOM 元素上触发事件时,这个事件并不会立即“凭空”发生并结束,而是会遵循一个特定的传播路径,途经它的所有祖先元素,直至顶层的 window 对象。这个过程就像在水中投下一颗石子,涟漪会从中心向外扩散。

1.1 DOM 事件流的三个阶段

根据 W3C 的标准,一个完整的 DOM 事件流包含三个阶段。假设我们点击了页面中的一个按钮,事件流会依次经历:

1.1.1 事件捕获(Capturing Phase)

事件从最外层的祖先元素(通常是 windowdocument)开始,逐级“向下”传播,直到“捕获”到触发事件的目标元素为止。这个阶段就像一个命令从总部下达到基层,层层传递。

1.1.2 目标阶段(Target Phase)

事件到达了它最初被触发的元素,也就是我们的“目标元素”(Target Element)。事件监听器如果在目标元素上注册,通常会在这个阶段被触发。

1.1.3 事件冒泡(Bubbling Phase)

事件从目标元素开始,逐级“向上”冒泡,经过每一个父元素,最终回到最顶层的 window 对象。这个阶段是事件流中我们最常利用的部分,它就像一个气泡从水底浮向水面。需要注意的是,几乎所有浏览器中的事件默认都工作在冒泡阶段。

1.2 可视化事件流

为了更直观地理解,我们可以用一个 Mermaid 流程图来展示这个过程。假设我们有如下 HTML 结构,并且我们点击了最内层的 p 标签:

<div id="outer"><div id="middle"><p id="inner">Click Me!</p></div>
</div>

二、控制事件流:addEventListener 的秘密

我们之前学习的 addEventListener 方法其实隐藏了一个控制事件流的“开关”——它的第三个参数。

element.addEventListener(type, listener, useCapture);

2.1 第三个参数 useCapture

这个参数是一个布尔值,用于指定事件监听器是在捕获阶段还是在冒泡阶段被触发。

2.1.1 useCapture = false (默认)

如果你省略第三个参数,或者将其设置为 false,那么事件监听器将在 冒泡阶段 执行。这是最常见的情况。

(1)代码示例

我们为上面 HTML 结构的三个 div 添加点击事件监听器。

<div id="outer" style="padding: 30px; background-color: lightblue;">Outer<div id="middle" style="padding: 30px; background-color: lightgreen;">Middle<p id="inner" style="padding: 30px; background-color: pink;">Inner (Click Me!)</p></div>
</div>
const outer = document.getElementById('outer');
const middle = document.getElementById('middle');
const inner = document.getElementById('inner');// 默认在冒泡阶段监听
outer.addEventListener('click', () => console.log('Bubble: Outer Div'));
middle.addEventListener('click', () => console.log('Bubble: Middle Div'));
inner.addEventListener('click', () => console.log('Bubble: Inner P'));

当你点击最内层的 p 元素时,控制台的输出顺序将是:

Bubble: Inner P
Bubble: Middle Div
Bubble: Outer Div

这清晰地展示了事件从内向外“冒泡”的过程。

2.1.2 useCapture = true

如果将第三个参数设置为 true,事件监听器将在 捕获阶段 执行。

(1)代码示例

我们将监听器全部改为在捕获阶段触发。

const outer = document.getElementById('outer');
const middle = document.getElementById('middle');
const inner = document.getElementById('inner');// 设置在捕获阶段监听
outer.addEventListener('click', () => console.log('Capture: Outer Div'), true);
middle.addEventListener('click', () => console.log('Capture: Middle Div'), true);
inner.addEventListener('click', () => console.log('Capture: Inner P'), true);

现在,再次点击最内层的 p 元素,你会看到完全相反的输出顺序:

Capture: Outer Div
Capture: Middle Div
Capture: Inner P

这证明了事件是从外向内“捕获”的。


三、管理事件传播:阻止与委托

理解了事件流,我们就可以更精确地控制它,从而实现一些高级的编程模式。

3.1 阻止事件传播:event.stopPropagation()

有时候,我们不希望事件在触发后继续传播(无论是冒泡还是捕获)。例如,点击一个子元素时,不希望触发其父元素的点击事件。这时,event 对象的 stopPropagation() 方法就派上用场了。

3.1.1 为什么需要阻止传播?

想象一个场景:你有一个带有关闭按钮的弹窗。弹窗本身(遮罩层)和关闭按钮都有点击事件。点击关闭按钮时,你只想关闭弹窗;但如果不做处理,点击事件会冒泡到弹窗遮罩层,可能会触发“点击遮罩层关闭弹窗”的逻辑,导致不必要的行为或错误。

3.1.2 如何使用 stopPropagation()

我们可以在事件处理函数中调用它。

(1)代码示例

在之前的冒泡示例中,我们在 Middle Div 的事件处理函数中阻止事件传播。

const outer = document.getElementById('outer');
const middle = document.getElementById('middle');
const inner = document.getElementById('inner');outer.addEventListener('click', () => console.log('Bubble: Outer Div'));
middle.addEventListener('click', (event) => {console.log('Bubble: Middle Div');event.stopPropagation(); // 在这里阻止事件继续冒泡!
});
inner.addEventListener('click', () => console.log('Bubble: Inner P'));

现在点击 p 元素,输出将会是:

Bubble: Inner P
Bubble: Middle Div

可以看到,事件传播到 middle 元素后就停止了,outer 元素的点击事件没有被触发。

3.2 事件委托(Event Delegation)

事件委托是 JavaScript 中一种极其重要且高效的事件处理模式。它巧妙地利用了事件冒泡机制。

3.2.1 什么是事件委托?

简单来说,事件委托就是将一组子元素的事件监听器,统一委托给它们的共同父元素来处理。你不需要为每一个子元素单独绑定事件,只需要在它们的父元素上绑定一个监听器即可。

3.2.2 事件委托的实现原理

  1. 利用事件冒泡:当子元素上的事件发生时,它会冒泡到父元素。
  2. 在父元素上监听:我们在父元素上设置的监听器此时会被触发。
  3. 识别事件源:在父元素的事件处理函数中,通过 event.target 属性,我们可以准确地知道是哪个子元素最初触发了该事件。
  4. 执行相应逻辑:根据 event.target 的信息,执行相应的代码。

3.2.3 代码实例:动态列表的点击事件

假设我们有一个 <ul> 列表,我们希望点击任何一个 <li> 时,都能打印出其内容。

<ul id="parent-list"><li>Item 1</li><li>Item 2</li><li>Item 3</li><li>Item 4</li>
</ul>
<button id="add-item">Add New Item</button>
const list = document.getElementById('parent-list');// 将事件监听器添加到父元素 <ul> 上
list.addEventListener('click', (event) => {// event.target 是我们实际点击的元素// 检查确保我们点击的是一个 LI 元素if (event.target && event.target.nodeName === 'LI') {console.log('You clicked on item:', event.target.textContent);}
});// 用于动态添加新列表项的按钮
const addButton = document.getElementById('add-item');
addButton.addEventListener('click', () => {const newItem = document.createElement('li');const itemCount = list.children.length + 1;newItem.textContent = `Item ${itemCount}`;list.appendChild(newItem);
});

在这个例子中,无论你点击已有的 <li> 还是点击按钮后新增的 <li>,点击事件都能被正确处理。这就是事件委托的魔力!

3.2.4 事件委托的优势

(1) 性能提升

页面上只需要创建一个事件监听器,而不是为每个子元素都创建。这极大地减少了内存占用和 DOM 操作,尤其是在子元素数量庞大的情况下,性能优势非常明显。

(2) 动态元素支持

当新的子元素被动态添加到父元素中时(如我们的例子),无需为新元素重新绑定事件监听器。因为事件是绑定在父元素上的,新元素自然而然地就“享受”到了事件委托的便利。这让代码维护变得更加简单。


四、常见问题与注意事项

4.1 event.target vs event.currentTarget

在事件委托中,区分这两个属性至关重要。

  • event.target:指向触发事件的原始元素(比如我们点击的那个 <li>)。
  • event.currentTarget:指向当前事件监听器所附加到的元素(在我们的委托示例中,它总是父元素 <ul>)。
list.addEventListener('click', (event) => {console.log('target:', event.target); // 可能是 <li>console.log('currentTarget:', event.currentTarget); // 永远是 <ul>
});

4.2 stopPropagation() vs preventDefault()

初学者常常混淆这两个方法。

  • event.stopPropagation()阻止事件在 DOM 树中进一步传播(冒泡或捕获)。它只影响事件流。
  • event.preventDefault()取消与事件关联的浏览器默认行为。例如,阻止点击链接 <a> 后的页面跳转,或者阻止点击提交按钮 <button type="submit"> 后的表单提交。

它们是完全不同的两件事,可以一起使用,也可以单独使用。


五、总结

恭喜你,又攻克了 JavaScript 中一个非常核心的概念!掌握事件流与事件委托,你的代码将提升到一个新的层次。让我们快速回顾一下今天的重点:

  1. DOM 事件流:包含三个阶段——捕获(从外到内)目标冒泡(从内到外)。浏览器默认在冒泡阶段处理事件。
  2. 控制事件流addEventListener(type, listener, useCapture) 的第三个参数 useCapture (布尔值) 决定了监听器在哪个阶段触发,true 为捕获,false (默认) 为冒泡。
  3. 阻止事件传播:使用 event.stopPropagation() 可以阻止事件继续在 DOM 树中传播,避免触发父级元素的同类事件。
  4. 事件委托:这是一种利用事件冒泡的高效模式。通过在父元素上设置单个监听器,来管理所有子元素的事件。
  5. 事件委托的优势提升性能(减少监听器数量)和支持动态元素(新添加的元素无需再次绑定事件)。
  6. 关键属性区分event.target 是事件的真正源头,而 event.currentTarget 是监听器所在的元素。

http://www.xdnf.cn/news/14505.html

相关文章:

  • 2、Java流程控制:编程界的“逻辑游乐场”
  • Leetcode 刷题记录 12 —— 二叉树第三弹
  • 六月十五号Leetcode
  • Apache Iceberg与Hive集成:非分区表篇
  • 【Redis】分布式锁
  • 我的项目管理之路-PMO
  • OpenSpeedy:让游戏体验“飞”起来的秘密武器
  • 基于CNN深度学习的小程序识别-视频介绍下自取
  • Android 修改了页面的xml布局,使用了databinding,这时候编译时需要用到apt吗
  • Node.js 中两种模块导出方式区别
  • Vue 组合式 API 与 选项式 API 全面对比教程
  • 期权入门介绍
  • PCB设计教程【大师篇】stm32开发板PCB布线(信号部分)
  • 附录:对于头结点单向链表的优化方法
  • AlibabaCloud+SpringCloud简述
  • 人工智能学习25-BP代价函数
  • RHCE 练习四:编写脚本实现以下功能
  • 10N65-ASEMI电机驱动方面专用10N65
  • 34.树形 DP
  • 【C语言】计算机组成、计算机语言介绍
  • 【Steel Code】8.6~8.7:TENSION MEMBERS, COMPRESSION MEMBERS
  • CountDownLatch入门代码解析
  • DeepSeek介绍
  • 嵌入式学习
  • Linux命令
  • JUC核心解析系列(一)——原子类深度解析
  • python第52天打卡
  • 模型 追蛇效应
  • 理解 C++ 中的特征技术(traits)
  • 基于vue框架的儿童食品营养推荐系统的设计与实现8t2b9(程序+源码+数据库+调试部署+开发环境)带论文文档1万字以上,文末可获取,系统界面在最后面。