从代码出发,一次性理解 JS 事件流

你好,我是沐爸,欢迎点赞、收藏、评论和关注。个人知乎

掌握 JavaScript 事件流可以深入理解事件在Web页面上的传播方式,这对于开发交互式和动态网页至关重要。理解事件流可以帮助你更有效地控制事件的行为,避免不必要的事件冲突,以及实现更精细的事件监听和处理逻辑。

今天分享下什么是事件流?事件流的三个阶段?如何控制事件流?如何阻止事件传播?如何阻止默认行为?什么是事件委托?

一、事件流

JS 事件流(也称为DOM事件流)是描述在DOM中事件如何传播的机制。当用户在浏览器中执行某些操作(如点击、按键)时,这些操作会触发事件。这些事件会遵循一定的顺序在DOM元素之间传播,这个顺序和过程就是事件流。

JS 事件流有三个阶段:、捕获阶段(Capturing Phase)、目标阶段(Target Phase)和冒泡阶段(Bubbling Phase)。

捕获阶段

在捕获阶段,事件从文档的根节点(通常是<html>元素)开始,沿着DOM树向下传播到事件的目标节点(即触发事件的元素)。在这个过程中,事件会经过目标节点的所有祖先节点。在捕获阶段,事件处理器可以阻止事件的进一步传播,即取消冒泡。

目标阶段

严格来说,目标阶段并不是事件流的一个独立阶段,而是事件到达其目标元素时的一个点。在这个阶段,事件在目标元素上被处理。然而,由于这个阶段通常包含在捕获阶段和冒泡阶段之间,因此它经常被提及。

冒泡阶段

在冒泡阶段,事件从目标节点开始,沿着DOM树向上传播到文档的根节点。在冒泡阶段,事件会触发所有注册了冒泡事件监听器的节点上的事件处理程序。这是事件流中最常用的阶段,因为大多数事件处理程序都是在这个阶段注册的。

二、事件监听器

可以使用事件侦听器 addEventListener 可以控制事件流。

在JavaScript中,addEventListener 方法用于向指定元素添加事件监听器,以处理该元素上的事件。该方法接受三个参数:事件名称、事件处理函数和一个可选的布尔值,第三个参数用于指定事件在捕获阶段触发(true)还是在冒泡阶段触发(false,默认值)。以此控制事件流。

冒泡执行(默认)

JS 事件的执行顺序默认由内向外冒泡执行,即内层元素的事件侦听器会先触发,外层元素的事件侦听器后触发。

<div id="outer" style="padding: 20px; border: 1px solid #000;">  外层元素  <div id="inner" style="padding: 10px; border: 1px solid #f00;">  内层元素  </div>  
</div><script>// 获取外层元素和内层元素var outerElement = document.getElementById("outer");var innerElement = document.getElementById("inner");// 为document添加点击事件监听器document.addEventListener("click", function () {console.log("document被点击了");});// 在外层元素上为点击事件outerElement.addEventListener("click", function (event) {console.log("外层元素被点击了");});// 在内层元素上为点击事件innerElement.addEventListener("click", function (event) {console.log("内层元素被点击了");});
</script>

以上代码,点击内层元素时,会依次打印"内层元素被点击了",“外层元素被点击了”,“document被点击了”

捕获执行

如果指定事件侦听器在捕获阶段触发,那么 JS 事件的执行顺序将由外向内执行,即外层元素的事件侦听器先触发,内层元素的事件侦听器会后触发。

<div id="outer" style="padding: 20px; border: 1px solid #000;">  外层元素  <div id="inner" style="padding: 10px; border: 1px solid #f00;">  内层元素  </div>  
</div><script>// 获取外层元素和内层元素var outerElement = document.getElementById("outer");var innerElement = document.getElementById("inner");// 为document添加点击事件监听器document.addEventListener("click", function () {console.log("document被点击了");},true // 设置捕获阶段触发);// 在外层元素上为点击事件outerElement.addEventListener("click",function (event) {console.log("外层元素被点击了");},true // 设置捕获阶段触发);// 在内层元素上为点击事件innerElement.addEventListener("click",function (event) {console.log("内层元素被点击了");});
</script>

以上代码的运行结果:

  • 点击内层元素时,会依次打印"document被点击了",“外层元素被点击了”,“内层元素被点击了”。
  • 点击外层元素时,会依次打印"document被点击了",“外层元素被点击了”。

变异情况

注意:当同时存在冒泡阶段执行和捕获阶段执行时,记住一个原则:捕获阶段先执行,冒泡阶段后执行。

示例一
<div id="outer" style="padding: 20px; border: 1px solid #000;">  外层元素  <div id="inner" style="padding: 10px; border: 1px solid #f00;">  内层元素  </div>  
</div><script>// 获取外层元素和内层元素var outerElement = document.getElementById("outer");var innerElement = document.getElementById("inner");// 为document添加点击事件监听器document.addEventListener("click", function () {console.log("document被点击了");},false // 冒泡阶段触发);// 在外层元素上为点击事件outerElement.addEventListener("click",function (event) {console.log("外层元素被点击了");},true // 设置捕获阶段触发);// 在内层元素上为点击事件innerElement.addEventListener("click",function (event) {console.log("内层元素被点击了");});
</script>

以上代码的运算结果:

  • 点击内层元素时,会依次打印"外层元素被点击了",“内层元素被点击了”,“document被点击了”。
  • 点击外层元素时,会依次打印"外层元素被点击了",“document被点击了”。
示例二
<div id="outer" style="padding: 20px; border: 1px solid #000;">  外层元素  <div id="inner" style="padding: 10px; border: 1px solid #f00;">  内层元素  </div>  
</div><script>// 获取外层元素和内层元素var outerElement = document.getElementById("outer");var innerElement = document.getElementById("inner");// 为document添加点击事件监听器document.addEventListener("click", function () {console.log("document被点击了");},true // 捕获阶段触发);// 在外层元素上为点击事件outerElement.addEventListener("click",function (event) {console.log("外层元素被点击了");},false // 冒泡阶段触发);// 在内层元素上为点击事件innerElement.addEventListener("click",function (event) {console.log("内层元素被点击了");});
</script>

以上代码的运算结果:

  • 点击内层元素时,会依次打印"document被点击了",“内层元素被点击了”,“外层元素被点击了”。
  • 点击外层元素时,会依次打印"document被点击了",“外层元素被点击了”。
示例三

为同一个元素设置多个事件,且不同阶段触发。

<div id="outer" style="padding: 20px; border: 1px solid #000;">  外层元素  <div id="inner" style="padding: 10px; border: 1px solid #f00;">  内层元素  </div>  
</div><script>// 获取外层元素和内层元素var outerElement = document.getElementById("outer");var innerElement = document.getElementById("inner");// 为document添加点击事件监听器document.addEventListener("click", function () {console.log("document被点击了");},// true);// 在外层元素上为点击事件outerElement.addEventListener("click",function (event) {console.log("外层元素被点击了");},false // 冒泡捕获阶段触发);// 在外层元素上为点击事件2outerElement.addEventListener("click",function (event) {console.log("外层元素被点击了2");},true // 设置捕获阶段触发);// 在内层元素上为点击事件innerElement.addEventListener("click",function (event) {console.log("内层元素被点击了");});
</script>

以上代码的运算结果:

  • 点击内层元素时,会依次打印"外层元素被点击了2",“内层元素被点击了”,“外层元素被点击了”,“document被点击了”。
  • 点击外层元素时,会依次打印"外层元素被点击了2",“外层元素被点击了”,“document被点击了”。

三、阻止事件传播

在事件流中,事件会在捕获阶段和冒泡阶段传播。但在某些情况下,我们可能只希望事件在捕获阶段或冒泡阶段被处理,而不希望它继续传播。此时,可以在事件处理函数中使用event.stopPropagation()方法来阻止事件进一步传播(无论是捕获阶段还是冒泡阶段)。

阻止向上冒泡

<div id="outer" style="padding: 20px; border: 1px solid #000">外层元素<div id="inner" style="padding: 10px; border: 1px solid #f00">内层元素</div>
</div><script>// 获取外层元素和内层元素var outerElement = document.getElementById("outer");var innerElement = document.getElementById("inner");// 为document添加点击事件监听器document.addEventListener("click", function () {console.log("document被点击了");});// 为外层元素添加点击事件监听器outerElement.addEventListener("click", function (event) {console.log("外层元素被点击了");// 这里原本会执行一些操作,但现在由于内层元素的阻止,这些操作在点击内层元素时不会执行});// 为内层元素添加点击事件监听器,并阻止事件冒泡innerElement.addEventListener("click", function (event) {event.stopPropagation(); // 阻止事件冒泡console.log("内层元素被点击了");// 这里执行内层元素特有的操作});
</script>

以上代码的运行结果:

  • 点击内层元素时,只会打印"内层元素被点击了"。
  • 点击外层元素时,会依次打印"外层元素被点击了"、“document被点击了”,因为外层元素依然向上冒泡。

双向阻止传递

在捕获阶段触发的事件监听器,如果设置了 event.stopPropagation(); 那么会同时阻止事件向上和向下传递,换句话说,只有点击元素自身,事件监听器才会触发。

<div id="outer" style="padding: 20px; border: 1px solid #000">外层元素<div id="inner" style="padding: 10px; border: 1px solid #f00">内层元素</div>
</div><script>// 获取外层元素和内层元素var outerElement = document.getElementById("outer");var innerElement = document.getElementById("inner");// 为document添加点击事件监听器document.addEventListener("click", function () {console.log("document被点击了");});// 为外层元素添加点击事件监听器outerElement.addEventListener("click",function (event) {event.stopPropagation();console.log("外层元素被点击了");},true);// 为内层元素添加点击事件监听器innerElement.addEventListener("click", function (event) {console.log("内层元素被点击了");});
</script>

以上代码的运行结果:

  • 点击内层元素时,只会打印"外层元素被点击了"。
  • 点击外层元素时,只会打印"外层元素被点击了"。

阻止向下传递

你可能会想,既然有阻止向上冒泡和阻止双向传递,那么应该有阻止向下传递啊,实际上是没有的。看下方代码:

<div id="outer" style="padding: 20px; border: 1px solid #000">外层元素<div id="inner" style="padding: 10px; border: 1px solid #f00">内层元素</div>
</div><script>// 获取外层元素和内层元素var outerElement = document.getElementById("outer");var innerElement = document.getElementById("inner");// 为document添加点击事件监听器document.addEventListener("click", function () {console.log("document被点击了");});// 为外层元素添加点击事件监听器outerElement.addEventListener("click", function (event) {event.stopPropagation();console.log("外层元素被点击了");});// 为内层元素添加点击事件监听器innerElement.addEventListener("click", function (event) {console.log("内层元素被点击了");});
</script>

以上代码的运行结果:

  • 点击内层元素时,会依次打印"内层元素被点击了",“外层元素被点击了”。
  • 点击外层元素时,只会打印"外层元素被点击了"。

四、阻止默认行为

有些事件有默认行为(如点击链接会跳转,表单提交会发送数据),但在某些情况下,我们可能不希望这些默认行为发生,而是想执行一些自定义的操作。此时,可以在事件处理函数中使用event.preventDefault()方法来阻止事件的默认行为。

示例

1.阻止链接的默认跳转

<a href="https://www.example.com" id="myLink">点击我不会跳转到example.com</a><script>// 获取链接元素var link = document.getElementById("myLink");// 为链接添加点击事件监听器link.addEventListener("click", function (event) {// 阻止链接的默认跳转行为event.preventDefault();// 你的其他代码...alert("链接被点击了,但浏览器不会跳转。");});
</script>

2.阻止表单的默认提交行为

<form id="myForm"><label for="name">姓名:</label><input type="text" id="name" name="name" /><br /><br /><input type="submit" value="提交" />
</form><script>// 获取表单元素var form = document.getElementById("myForm");// 为表单添加提交事件监听器form.addEventListener("submit", function (event) {// 阻止表单的默认提交行为event.preventDefault();// 校验表单数据if (form.name.value === "") {alert("姓名不能为空!");return;}// 你的其他代码,如通过AJAX提交表单数据等console.log("表单提交成功!");});
</script>

五、事件委托

事件委托是一种优化事件处理的技术,它利用事件冒泡的原理,只在父元素上设置事件监听器,然后通过事件对象的target属性来判断事件是否发生在特定子元素上,从而执行相应的处理函数。这种方法可以减少事件监听器的数量,提高性能,特别适用于大量子元素或动态内容较多的页面。

示例

例如,在一个列表中,可能有很多项(li元素),如果每项都绑定一个点击事件监听器,那么当列表项数量很多时,会严重影响性能。此时,可以使用事件委托,只在列表的父元素上绑定一个点击事件监听器,然后在事件处理函数中判断点击的是哪个列表项,并据此执行相应的操作,并且能够处理动态添加到DOM中的元素。

<ul id="myList"><li>列表项 1</li><li>列表项 2</li><!-- 可以有更多或动态添加的 li 元素 -->
</ul>
<button id="addLi">添加更多列表项</button><script>// 获取 ul 元素var ul = document.getElementById("myList");// 为 ul 添加点击事件监听器ul.addEventListener("click", function (event) {// 检查点击事件的目标(target)是否为 li 元素if (event.target && event.target.nodeName === "LI") {// 是 li 元素,执行相应的操作event.target.style.backgroundColor = "pink"; // 例如,改变背景色console.log(event.target.innerText); // 打印点击的 li 元素的文本内容}});// 添加更多列表项的按钮点击事件document.getElementById("addLi").addEventListener("click", function () {var newLi = document.createElement("li"); // 创建一个新的 li 元素newLi.textContent = "新列表项 " + (ul.children.length + 1); // 设置 li 的文本内容ul.appendChild(newLi); // 将新的 li 元素添加到 ul 中// 注意:由于使用了事件委托,新添加的 li 元素自动具有点击事件效果});
</script>

好了,分享结束,谢谢点赞,下期再见。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/web/51282.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

图解Redis五大数据类型

五种数据类型的不同之处&#xff0c;是value在存储时的形式不同。 hash类型 value类型是<key,value>键值对。如果发生hash冲突&#xff0c;用开放定址法解决&#xff0c;不拉链&#xff01; key值重复&#xff0c;则新值覆盖旧值 List类型 Set类型 与List的类似&…

3款一键AI智能改写软件,轻松改出高质量文章 tzq

随着科技的不断发展&#xff0c;人工智能技术正逐渐渗透到各个领域。在写作方面&#xff0c;一键AI智能改写软件的出现&#xff0c;为人们提供了更加便捷高效的写作工具。无论是写作初学者还是专业作家&#xff0c;都可以通过这些软件轻松改写出高质量的文章。本文将介绍三款优…

查找数学类文献的专业数据库有哪些 如何获取这些数据库资源

一、MathSciNet&#xff08;美国数学会《数学评论》&#xff09; MathSciNet数据库是美国数学学会出版的《数学评论》Mathematical Reviews和Current Mathematical Publications的网络版&#xff0c;包含《数学评论》自1940年出版以来的所有评论文章&#xff0c;包括期刊、图书…

电子电气架构---车载网简史(下)

我是穿拖鞋的汉子,魔都中坚持长期主义的汽车电子工程师。 老规矩,分享一段喜欢的文字,避免自己成为高知识低文化的工程师: 屏蔽力是信息过载时代一个人的特殊竞争力,任何消耗你的人和事,多看一眼都是你的不对。非必要不费力证明自己,无利益不试图说服别人,是精神上的节…

【AI+编程】只需1句提示词0代码生成前端展示效果

最近被Vercel发布的V0 编程效果惊艳到了&#xff0c; 不管是前端开发 还是立志成为全栈工程师的 同学&#xff0c;不可错过。 官网地址&#xff1a;https://v0.dev/chat/ 代码生成工具很多&#xff0c;不管是github copilot、阿里的通义灵码&#xff0c; 腾讯云的AI代码助手…

PyTorch升级之旅——主要组成模块

本文仅作为个人学习记录使用 文章目录 前言 一、深度学习的简单流程 二、基本配置 三、数据读入 四、模型构建 五、模型初始化 六、损失函数 七、训练和评估 八、可视化 九、PyTorch优化器 总结 前言 学习链接&#xff1a;第三章&#xff1a;PyTorch的主要组成模块…

leetcode 80 删除有序数组中的重复项 II

正文 仍旧使用双指针&#xff0c; 思想与 leetcode 26 删除有序数组中的重复项 一致。只是此时因为要求保留重复元素两次&#xff0c;我们的左侧指针可以从第二个数据开始&#xff0c;且右侧指针需要和两个元素的值进行判断。 class Solution:def removeDuplicates(self, nums…

深度学习入门:循环神经网络------RNN概述,词嵌入层,循环网络层及案例实践!(万字详解!)

目录 &#x1f354; RNN 概述 1.1 循环神经网络 1.2 自然语言处理 &#x1f354; 词嵌入层 2.1 词嵌入层的使用 2.2 关于词嵌入层的思考 2.3 小节 &#x1f354; 循环网络层 3.1 RNN 网络原理 3.1.1 RNN计算过程 3.1.2 如何计算神经元内部 3.2 PyTorch RNN 层的使用…

总结:Python语法

Python中的字典、列表和数组是三种常用的数据结构&#xff0c;它们各自有不同的用途和特性。 字典&#xff08;Dictionary&#xff09; 字典是一种无序的、可变的数据结构&#xff0c;它存储键值对&#xff08;key-value pairs&#xff09;。字典中的每个元素都是一个键值对&…

Flask返回Json格式字符,中文导致unicode乱码问题

一.问题描述 或者直接返回json格式的字符串 从上图可以看出&#xff0c;当flask实现的接口响应中存在中文时&#xff0c;接口返回json字串的中文为unicode乱码。 二.问题解决 百度搜索了很多&#xff0c;原来在创建flask app时使用json格式的字符串&#xff0c;默认是ascii编…

字节微前端框架Garfish

Garfish 是字节跳动开源的微前端框架&#xff0c;旨在应对现代 Web 应用在前端生态繁荣与应用日益复杂化背景下的挑战。本文将介绍如何使用 Garfish&#xff0c;提供代码示例&#xff0c;并与另一流行的微前端框架 Qiankun 进行对比分析。 安装 Garfish 首先&#xff0c;安装…

快速排序模版

1.霍尔法 #include <iostream> using namespace std; int partition(int *arr,int left,int right){int pivotIndexleft;while(left<right){while(left<right && arr[right]>arr[pivotIndex]){right--;}while(left<right && arr[left]<a…

vTable实现多维表格

介绍 vTable是字节开发的一款能用来渲染表格的库&#xff0c;是用canvas渲染&#xff0c;避免了传统用dom组件表格的一些问题&#xff0c;能很快的渲染出上万格子的表格。 接下来我将使用vTable构建类似下面的多维表格&#xff0c;其中quantity、sales等是指标。 使用 官网地址…

QT的基础数据类型(下)

QVector 与QList类似,但它在内存使用上更为紧凑,内部使用动态数组来存储元素,适用于存储大量相同类型的数据。 初始化 QVector的初始化方式有以下几种: //初始化一个空的QVectorQVector<int> vec;//初始化一个大小为10的空QVectorQVector<int> vec2(10);//使…

【精选】基于大数据的___银行信用卡用户的数仓系统的设计与实现(全网独一无二,最新定制)

目录&#xff1a; 关键技术介绍 PYTHON语言简介 大数据介绍 MySql数据库 DJANGO框架 Hadoop介绍 Scrapy介绍 B/S架构 系统的设计 系统总功能模块设计 系统测试 系统测试的目的 软件测试过程 6.3测试用例 参考代码&#xff1a; 为什么选择我&#xff1a; 博主介绍&am…

github访问加速项目@一键部署自动更改host修改加速Github访问

文章目录 abstractpowershell 版本的一键更新hosts文件更新Hosts 操作步骤准备:设置powershell执行策略powrshell脚本注册计划任务定期自动执行上述操作相关目录结构 其他方法获取相关脚本一键运行整合脚本&#x1f916;&#x1f43d;检查 abstract 尽管这里实现了一键部署自动…

自动驾驶-机器人-slam-定位面经和面试知识系列10之高频面试题(04)

这个博客系列会分为C STL-面经、常考公式推导和SLAM面经面试题等三个系列进行更新&#xff0c;基本涵盖了自己秋招历程被问过的面试内容&#xff08;除了实习和学校项目相关的具体细节&#xff09;。在知乎和牛客也会同步更新&#xff0c;全网同号&#xff08;lonely-stone或者…

Elasticsearch安装 Kibana安装

安装Elasticsearch 一、拉取镜像或者上传 docker pull Elasticsearch 二、将上传的镜像导入(在仓库拉取的这一步跳过) docker load -i es.tar docker load -i 三、创建容器 1.Elasticsearch 注意修改到自己的网络&#xff08;第八行&#xff09; docker run -d \--nam…

实时多个人脸跟踪算法实现过程记录【实时MOT】

实现的功能 之前做的实时多个人脸跟踪&#xff0c;现在记录一下。 摄像头检测到多个人脸&#xff0c;再进行跟踪。 现有问题&#xff1a;如果直接调用opncv的人脸跟踪&#xff0c;耗时多&#xff0c;无法做到实时。 具体实现过程 分为两个步骤&#xff1a;1.选base 2. 做优化…

2014年4月-2023年上市公司秩鼎ESG评级数据

2014年4月-2023年上市公司秩鼎ESG评级数据 1、时间&#xff1a;2014年4月-2023年11月 2、来源:秩鼎数据 3、指标&#xff1a;证券代码、SC、评级日期、ESG评级、ESG等级、ESG得分、E评级、E等级、E得分、S评级、S等级、S得分、G评级、G等级、G得分、总市值(亿元)、流通市值(…