你真的理解事件绑定、事件冒泡和事件委托吗?

一文了解Web API中的事件绑定、事件冒泡、事件委托

  • 引言
  • 正文
  • 一、事件绑定
    • 1、事件和事件绑定时什么?
    • 2、事件是如何实现的?
  • 二、事件冒泡
    • 1、事件模型
    • 2、事件模型解析
      • (1)捕获阶段
      • (2)目标阶段
      • (3)冒泡阶段
    • 3、addEventListener语法
    • 4、事件冒泡和事件捕获举例
      • (1)事件冒泡
      • (2)事件捕获
      • (3)事件捕获VS事件冒泡
  • 三、事件代理(事件委托)
  • 四、总结和回顾
  • 结束语

引言

事件,是JS Web API比较重要的一个知识点。我们平常所看到的网页,肯多内容都要用到事件。比如说一个点击、一个下拉、一个滚动,都要用到事件进行操作。

正文

一、事件绑定

1、事件和事件绑定时什么?

事件,就是可以被 js 捕获的人为的操作。那什么是人为的操作呢?比如说鼠标的点击、拖动、缩放网页等等行为,且在这些行为被 js 捕获到以后,就是事件。

举个例子:

比如说我现在要去楼下 舍管阿姨帮我开个门禁,那这个 的操作就是一个事件,就相当于在 js 里喊一个函数去干活。

说完事件,接下来说说事件绑定

什么是 javascript 的事件绑定呢?

用上面那个例子来继续阐述。 这个动作是一个事件,那我要怎么样才能做出 这个动作呢?就需要对我这个动作进行一个绑定。可以通过绑定一个函数,这个函数解决了我怎么喊出来的问题。比如,我要去楼下喊,那这个函数里面就说明了我需要去楼下喊的这个过程。

所以,事件绑定可以理解为,在有一个触发事件的前提下,后面紧跟着一个事件处理函数,这个函数里面包含着所要执行动作的具体过程等,这就是事件绑定。

接下来我们用代码来写一个事件绑定的过程。

function bindEvent(elem, type, fn){elem.addEventListener(type, fn);
}const btn1 = document.getElementById('btn1');
bindEvent( btn1 , 'click', event => {console.log(event.target); //event.target为获取触发的元素event.preventDefault(); //阻止默认行为alert('clicked');
});

浏览器显示效果如下。

事件绑定

大家可以看到,通过点击按钮这个事件,获取到触发的元素,这就是一个事件绑定。

2、事件是如何实现的?

事件基于发布订阅模式,就是在浏览器加载的时候会读取事件相关的代码,但是只有实际等到具体的事件触发的时候才会执行。

比如点击按钮,这是个事件 Event ,而负责处理事件的代码段通常被称为事件处理程序 Event Handler ,也就是「启动对话框的显示」这个动作。

Web 端,我们常见的就是 DOM 事件:

  • DOM0 级事件,直接在 html 元素上绑定 on-event ,比如 onclick ,取消的话, dom.onclick = null同一个事件只能有一个处理程序,后面的会覆盖前面的。
  • DOM2 级事件,通过 addEventListener 注册事件,通过 removeEventListener 来删除事件,一个事件可以有多个事件处理程序,按顺序执行,捕获事件和冒泡事件。
  • DOM3级事件,增加了事件类型,比如 UI 事件,焦点事件,鼠标事件。
    • UI事件,即当用户与界面上的元素交互时触发。
    • 焦点事件,即当用元素获得或失去焦点时触发。
    • 鼠标事件,当用户通过鼠标在页面上执行操作时触发。

二、事件冒泡

1、事件模型

W3C中定义的DOM事件流的发生经历三个阶段:捕获阶段(capturing)、目标阶段(targetin)、冒泡阶段(bubbling)。

  • 冒泡型事件:当你使用事件冒泡时,子级元素先触发,父级元素后触发。
  • 捕获型事件:当你使用事件捕获时,父级元素先触发,子级元素后触发。

2、事件模型解析

我们用 W3C 标准的 DOM 事件流模型图来看事件捕获事件冒泡DOM事件流

W3C事件模型

从图中可以看出,元素事件响应在 DOM 树中是从顶层的Window开始,流向目标元素(2),然后又从目标元素流向顶层的Window。

通常,我们将这种事件流向分为(1)捕获阶段,(2)目标阶段,(3)冒泡阶段。-> 序号对应图中的编号

(1)捕获阶段

捕获阶段是指,事件响应从最外层的Window开始,逐层向内层递进,直到到达具体的事件目标元素,如上图中的(1)。同时在捕获阶段,不会处理响应元素注册的冒泡事件。

(2)目标阶段

目标阶段指触发事件的最底层的元素,如上图中的(2)。

(3)冒泡阶段

冒泡阶段与捕获阶段相反,事件的响应是从最底层开始一层一层往外传递到最外层的Window,即一层一层往上冒,如上图中的(3)。

3、addEventListener语法

现在,我们知道了 DOM 事件流的三个阶段分别是先捕获阶段,然后是目标阶段,最后是冒泡阶段。这也就是我们平常所看到的一些面试题里面说的先捕获后冒泡的原因了。到此,相信大家对 DOM 事件流会有一个清晰的了解。

在实际操作中,我们可以通过 element.addEventListener() 函数来设置一个元素的事件模型,具体设置值可以设置为冒泡事件或捕获事件。

先来看下 addEventListener 函数的基本语法:

element.addEventListener(type, listener, useCapture);

其中,三个参数的含义如下:

type:监听事件类型的字符串;

listener:事件监听的回调函数,即事件触发后要处理的函数;

useCapture:默认值为false,表示事件冒泡;当设置为true时,表示事件捕获。

4、事件冒泡和事件捕获举例

接下来我们用几个实例来运用事件冒泡和事件捕获。

(1)事件冒泡

先附上一段代码。

<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><meta http-equiv="X-UA-Compatible" content="IE=edge"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>Document</title>
</head>
<style>#a{background-color: darkcyan;line-height: 40px;color: cornsilk;}#b{background-color: chocolate;}#c{background-color: cornflowerblue;}
</style>
<body><div id="a">事件a<div id="b">事件b<div id="c">事件c</div></div></div><script>let a = document.getElementById('a');let b = document.getElementById('b');let c = document.getElementById('c');//注册冒泡事件监听器a.addEventListener('click', () => {console.log("冒泡a")});b.addEventListener('click', () => {console.log('冒泡b')});c.addEventListener('click', () => {console.log("冒泡c")});</script>
</body>
</html>

当我们点击 事件c 时,浏览器执行结果如下:

事件冒泡举例

如我们所预想的,冒泡是从下往上冒泡,所以最终的执行顺序为 事件c → 事件b → 事件a ,打印出 冒泡c → 冒泡b → 冒泡a

(2)事件捕获

先附上一段代码。

<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><meta http-equiv="X-UA-Compatible" content="IE=edge"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>Document</title>
</head>
<style>#a{background-color: darkcyan;line-height: 40px;color: cornsilk;}#b{background-color: chocolate;}#c{background-color: cornflowerblue;}
</style>
<body><div id="a">事件a<div id="b">事件b<div id="c">事件c</div></div></div><script>let a = document.getElementById('a');let b = document.getElementById('b');let c = document.getElementById('c');//注册捕获事件监听器a.addEventListener('click', () => {console.log("捕获a")}, true);b.addEventListener('click', () => {console.log('捕获b')}, true);c.addEventListener('click', () => {console.log("捕获c")}, true);</script>
</body>
</html>

此时,我们给 addEventListener 加上 true 的属性,因此,当我们点击 事件c 时,浏览器执行结果如下:

事件捕获举例

如我们所预想的,捕获是从上往下捕获,也就是从外层向里层捕获,所以最终的执行顺序为 事件a → 事件b → 事件c ,打印出 捕获a → 捕获b → 捕获c

(3)事件捕获VS事件冒泡

接下来,我们将上述的代码 事件abc 三个元素都注册上捕获和冒泡事件,并以 事件c 作为触发事件的主体,即事件c为事件流中的目标阶段。

附上一段代码。

<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><meta http-equiv="X-UA-Compatible" content="IE=edge"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>Document</title>
</head>
<style>#a{background-color: darkcyan;line-height: 40px;color: cornsilk;}#b{background-color: chocolate;}#c{background-color: cornflowerblue;}
</style>
<body><div id="a">事件a<div id="b">事件b<div id="c">事件c</div></div></div><script>let a = document.getElementById('a');let b = document.getElementById('b');let c = document.getElementById('c');//注册冒泡事件监听器a.addEventListener('click', () => {console.log("冒泡a")});b.addEventListener('click', () => {console.log('冒泡b')});c.addEventListener('click', () => {console.log("冒泡c")});//注册捕获事件监听器a.addEventListener('click', () => {console.log("捕获a")}, true);b.addEventListener('click', () => {console.log('捕获b')}, true);c.addEventListener('click', () => {console.log("捕获c")}, true);</script>
</body>
</html>

当我们点击 事件c 时,浏览器执行结果如下:

事件冒泡vs事件捕获

如我们所预想的,先对事件进行捕获,后再对事件进行冒泡。当捕获时,事件从外往内捕获,所以打印结果是冒泡是 捕获a → 捕获b → 捕获c当冒泡时,事件由内往外冒泡,所以最终的打印结果为 冒泡c → 冒泡b → 冒泡a

三、事件代理(事件委托)

讲完事件冒泡和事件代理,那么对于事件代理就比较容易理解了。

事件代理,即事件委托。事件代理就是利用事件冒泡或者事件捕获的机制把一系列的内层元素事件绑定到外层元素上。

我们来看个例子。

<ul id="item-list"><li>item 1</li><li>item 2</li><li>item 3</li><li>item 4</li>
</ul>

比如说,我们要给这个 ul 列表下面的每个 li 元素绑定事件。如果按照传统方法处理的话,我们可能会一个一个去绑定。数据量小的时候可能还好,但如果遇到数据量大的时候呢?一个一个绑定也太可怕了。

因此就有了事件代理。我们可以通过使用事件代理,将绑定多个事件的操作变为只绑定一次的操作,这样就极大减少了代码的重复编写。

因此,利用事件冒泡或事件捕获,来达到事件代理的效果。具体实现方式如下:

<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><meta http-equiv="X-UA-Compatible" content="IE=edge"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>Document</title>
</head>
<body><ul id="item-list"><li>item 1</li><li>item 2</li><li>item 3</li><li>item 4</li></ul><script>let items = document.getElementById('item-list');//通过事件冒泡实现事件代理items.addEventListener('click', (e) => {console.log('冒泡:click ',e.target.innerHTML)}, false);//通过事件捕获实现事件代理items.addEventListener('click', (e) => {console.log('捕获:click ',e.target.innerHTML)}, true);</script>
</body>
</html>

当点击列表中的 item 时,执行结果如下:

事件代理

从上图中可以看出,当点击目标元素时,可以对其进行捕获,在捕获结束后,对其进行冒泡操作,且达到了点击当前元素只显示当前元素的效果。

同时,细心的小伙伴已经发现,在我们上面的代码中,编写顺序是先冒泡后捕获。但结果打印依然是先捕获后冒泡。这也就顺应了我们上面所说的,关于DOM事件流的顺序,都是先捕获后冒泡,而跟实际的代码顺序是没有关系的。

四、总结和回顾

讲完事件绑定、DOM事件流模型中的事件冒泡和事件捕获以及事件代理,我们来做个总结和回顾。

(1)以上的内容总结下来有以下几点:

  • DOM事件流有3个阶段:捕获阶段,目标阶段,冒泡阶段。三个阶段的顺序为:捕获阶段 → 目标阶段 → 冒泡阶段。

  • 对于目标阶段和非目标阶段的元素,事件响应执行顺序都遵循先捕获后冒泡的原则。

    注:目标阶段即当前所点击事件,即为目标阶段。非目标阶段即外围所影响的事件即为非目标阶段。

  • 事件捕获是从顶层的Window逐层像内层执行,事件冒泡则相反;

  • 事件代理(即事件委托)是根据事件冒泡或事件捕获的机制来实现的。

(2)用几个题目来回顾下我们上面所讲的知识点

Q1:描述事件冒泡的流程

A1:

  • 基于DOM树形结构
  • 事件会顺着所触发的元素,一层一层的往上冒
  • 应用场景:事件代理

Q2:当无限下拉图片列表时,如何监听每个图片的点击?

A2:

  • 用事件代理处理
  • 用e.target获取触发元素
  • 用matches来判断是否触发元素

结束语

一直都不是特别清楚为什么是事件是先捕获后冒泡,脑子里也没有个大概框架,文绉绉的文字也不能让我对它有所理解。直到看到了 W3C 的那张 DOM 事件流模型的图,一下子明白了事件为什么是先捕获后冒泡了。因为 Window 对象是直接面向用户的,那么当用户触发一个事件时,如点击事件,肯定时从 Window 对象开始的,然后再向内逐层递进。所以自然也就是先捕获后冒泡了!

关于Web API中的事件就讲到这里啦!如有疑问欢迎评论区评论或私信我交流~

  • 关注公众号 星期一研究室 ,不定期分享学习干货

  • 如果这篇文章对你有用,记得点个赞加个关注再走哦~

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

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

相关文章

欢迎来到 C# 9.0(Welcome to C# 9.0)

翻译自 Mads Torgersen 2020年5月20日的博文《Welcome to C# 9.0》&#xff0c;Mads Torgersen 是微软 C# 语言的首席设计师&#xff0c;也是微软 .NET 团队的项目群经理。C# 9.0 正在成形&#xff0c;我想和大家分享一下我们对下一版本语言中添加的一些主要特性的想法。对于 C…

367. 有效的完全平方数(二分法)

一&#xff1a;题目 二:思路 完全平方数:若一个数能表示成某个整数的平方的形式&#xff0c;则称这个数为完全平方数 思路:1.我们将num先折半,因为它是某个整数的平方&#xff0c;而这个数的范围肯定不会超过num的一半 2.那么这就相当于在[left,num/2]中查找某个数&#xff0c…

译 | Azure 应用服务中的程序崩溃监控

点击上方蓝字关注“汪宇杰博客”原文&#xff1a;Yun Jung Choi, Puneet Gupta翻译&#xff1a;汪宇杰应用程序崩溃经常发生。崩溃是指代码中的异常未得到处理并终止进程。这些未处理的异常也称为二次机会异常&#xff08;second chance exceptions&#xff09;。当您的应用程序…

使用Seq搭建免费的日志服务

Seq简介Seq是老外开发的一个针对.NET平台非常友好的日志服务。支持容器部署&#xff0c;提供一个单用户免费的开发版本。官网&#xff1a;https://datalust.co/seq使用文档&#xff1a;https://docs.datalust.co/docsSeq主体功能如下所示&#xff1a;支持主流的编程语言&#x…

leetcode27:移除元素(暴力+双指针)

一&#xff1a;题目 二&#xff1a;暴力双指针 1&#xff1a;暴力解法 (1):思路 1.在数组当中 我们想要删除一个元素 得靠覆盖也就是后面的元素往前覆盖其想要删除的元素 但是注意的是我们真实的数组中的元素个数是不变的 因为我们只是将后面的元素移到起前面 并未真正的删除…

三分钟Docker-推送本地镜像到仓库

在上篇文章中&#xff0c;我们完成了应用程序容器化&#xff0c;把webapi项目构建镜像并容器化运行。本文将会演示如何把自己构建的镜像上传到docker官网的仓库和自己私有仓库本地镜像推送到官网的registry1.创建仓库点击Docker Desktop图标->Repositories-》create 跳转到…

你知道304吗?图解强缓存和协商缓存

http协议—常见状态码&#xff0c;请求方法&#xff0c;http头部&#xff0c;http缓存一、http状态码1、引例阐述2、状态码分类3、常见状态码4、关于协议和规范二、http 方法1、传统的methods2、现在的methods3、Restful API&#xff08;1&#xff09;Restful API是什么&#x…

leetcode844. 比较含退格的字符串(栈+双指针)

一:题目 二:思路代码 1:利用栈 (1):思路 1.利用栈 我们将字符串中的单个元素都入栈 当遇到’#的时候将将栈顶元素弹出 (2):上码&#xff08;方法一&#xff09; class Solution { public:/**思路:1.利用栈 我们将字符串中的单个元素都入栈 当遇到#的时候将将栈顶元素弹出*…

efcore技巧贴-也许有你不知道的使用技巧

前言.net 环境近些年也算是稳步发展。在开发的过程中&#xff0c;与数据库打交道是必不可少的。早期的开发者都是DbHelper一撸到底&#xff0c;到现在的各种各样的ORM框架大行其道。孰优孰劣谁也说不清楚&#xff0c;文无第一武无第二说的就是这个理。没有什么最好的&#xff0…

关于前端性能优化问题,认识网页加载过程和防抖节流

前端性能优化—网页加载过程、性能优化方法、防抖和节流一、网页加载过程1、加载资源的形式2、加载资源的过程3、渲染页面的过程4、关于window.onload 和 DOMContentLoaded二、性能优化1、性能优化原则2、性能优化的方法3、让加载更快4、让渲染更快三、防抖和节流1、防抖 debou…

javax.net.ssl.SSLHandshakeException: No appropriate protocol

一:报错 二:解决 找到jdk 1.8安装目录&#xff0c;找到C:\Program Files\Java\jre里面的lib\security 下面有个java.security将jdk.tls.disabledAlgorithms后面的SSLv3, TLSv1, TLSv1.1都删除掉.&#xff08;大概位置是在700多行&#xff09; 三:上方并未解决的 我的jdk是这…

『软件工程9』结构化系统分析——解决软件“做什么”问题

结构化系统分析——解决软件“做什么”问题一、系统分析的任务和过程1、系统分析的任务2、系统分析的过程&#xff08;1&#xff09;问题识别&#xff08;2&#xff09;分析与综合&#xff08;3&#xff09;编制文档&#xff08;4&#xff09;系统分析评审二、结构化分析方法1、…

.NET5.0 Preview 8 开箱教程

.NET5.0 Preview 8 开箱教程前言首先&#xff0c;看到 .NET5.0 Preview 8 发布后&#xff0c;作为一枚基层应用开发人员&#xff0c;很想要体验一下新版本的魅力&#xff1b;这可能就是程序员对新技术的一种执着吧。其实从官方宣布 .NETCore 将更名为 .NET5 开始&#xff0c;我…

leetcode977. 有序数组的平方(暴力+双指针)

一:题目 二:暴力双指针 1:暴力 class Solution { public:vector<int> sortedSquares(vector<int>& nums) {vector<int> v;for(int num : nums){int temp pow(num,2);v.push_back(temp);} sort(v.begin(),v.end());return v;} };2:双指针 思路:1.利…

『软件工程10』结构化系统分析:数据流图和字典案例分析

结构化系统分析——数据流图和数据字典案例分析一、数据流图案例分析1、案例1&#xff1a;商店业务管理系统2、案例2&#xff1a;学籍管理系统3、案例3&#xff1a;大型企业数据中心二、数据字典案例分析1、案例1&#xff1a;学籍管理系统三、写在最后接 上一篇文章的内容&…

MongoDB最新4.2.7版本三分片集群修改IP实操演练

背景重新组网&#xff0c;需要对现有MongoDB分片集群服务器的IP进行更改&#xff0c;因此也需要对MongoDB分片集群的IP也进行相应的更新&#xff0c;而MongoDB分片集群的IP修改不能单纯的通过配置来进行&#xff0c;需要一番折腾后才能正常更新&#xff0c;这里对整个MongoDB集…

浅谈Web前端安全策略xss和csrf,及又该如何预防?

Web前端安全策略xss和csrf的攻击和防御一、XSS跨站请求攻击1、什么是XSS2、场景模拟3、XSS的攻击类型4、如何防御XSS二、XSRF跨站请求伪造1、什么是csrf2、场景模拟&#xff08;1&#xff09;场景一&#xff08;2&#xff09;场景二3、CSRF的特点4、CSRF攻击方式5、CSRF常见的攻…

leetcode209. 长度最小的子数组(暴力+滑动窗口)

一:题目 二:暴力滑动窗口 1:暴力解法 class Solution { public:int min (int a ,int b){return a < b ? a : b;}int minSubArrayLen(int target, vector<int>& nums) {int minx 100001;for(int i 0; i < nums.size(); i){vector<int> v;int sum nu…

做权限认证,还不了解IdentityServer4?不二话,赶紧拥抱吧,.NET Core官方推荐!...

目前大多数的应用程序或多或少看起来是上图所示这样的&#xff0c;最常见的交互场景有&#xff08;浏览器与Web应用程序、Web应用程序与WebApi通讯、本地应用程序狱WebApi通讯、基于浏览器的应用程序与WebApi 通讯、基本服务器的应用程序与WebApi通讯、WebApi与WebApi通讯&…

leetcode 904:水果成篮(滑动窗口)

一:题目 二:思路 1.用两个篮子装进两个数&#xff0c;后面只能装入这两个相同的数,并统计个数;如果遇到其他数,则重新开始计数&#xff0c; 这里的重新开始计数指的是在去除第一个篮子中所装进的数 2.滑动窗口来做 滑动窗口的起始位置为:数组的起始位置 滑动体为 统计的个数 滑…