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

你真的理解事件委托(事件代理)吗?

目录

1. 基本概念

1.1 原理

2. 事件冒泡和事件捕获

代码演示

3. addEventListener的第三个参数

4. 事件委托阶段案例

4.1 事件冒泡案例

 4.2 事件捕获案例

 5. 经典面试题 

 6. 事件代理总结

6.1 冒泡还是捕获?

6.2 阻止事件冒泡

6.4 阻止默认事件

1. 基本概念

        什么叫事件委托呢?它还有一个名字叫事件代理,JavaScript高级程序设计上讲:事件委托就是利用事件冒泡,只指定一个事件处理程序,就可以管理某一类型的所有事件。

1.1 原理

        事件代理(Event Delegation),又称之为事件委托。是JavaScript中常用绑定事件的常用技巧。顾名思义,“事件代理”即是把原本需要绑定在子元素的响应事件(click、keydown......)委托给父元素,让父元素担当事件监听的职务。事件代理的原理是DOM元素的事件冒泡。       

举个通俗的例子

        比如一个宿舍的同学同时快递到了,一种方法就是他们一个个去领取,还有一种方法就是把这件事情委托给宿舍长,让一个人出去拿好所有快递,然后再根据收件人一 一分发给每个宿舍同学;

        在这里,取快递就是一个事件,每个同学指的是需要响应事件的 DOM 元素,而出去统一领取快递的宿舍长就是代理的元素,所以真正绑定事件的是这个元素,按照收件人分发快递的过程就是在事件执行中,需要判断当前响应的事件应该匹配到被代理元素中的哪一个或者哪几个。

2. 事件冒泡和事件捕获

        前面提到事件委托的原理是DOM元素的事件冒泡,那么事件冒泡是什么呢?

        事件冒泡就是事件从最深的节点开始,然后逐步向上传播事件。举个例子:页面上有这么一个节点树,div>ul>li>a;比如给最里面的a加一个click点击事件,那么这个事件就会一层一层的往外执行,执行顺序a>li>ul>div,有这样一个机制,那么我们给最外面的div加点击事件,那么里面的ul,li,a做点击事件的时候,都会冒泡到最外层的div上,所以都会触发,这就是事件委托,委托它们父级代为执行事件。

        一个事件触发后,会在子元素和父元素之间传播(propagation)。这种传播分成三个阶段

        三个阶段:捕获阶段 => 目标阶段 => 冒泡阶段

如上图所示,事件传播分成三个阶段:

  • 捕获阶段:从window对象传导到目标节点(上层传到底层)称为“捕获阶段”(capture phase),捕获阶段不会响应任何事件;
  • 目标阶段:在目标节点上触发,称为“目标阶段”
  • 冒泡阶段:从目标节点传导回window对象(从底层传回上层),称为“冒泡阶段”(bubbling phase)。事件代理即是利用事件冒泡的机制把里层所需要响应的事件绑定到外层;

捕获阶段:
从上到下,从window到你点击的目标节点,不如点击一个 input
window => body => inpiut => body => window

代码演示

<body>
    <div id="parent" class="flex-center">
        parent
        <p id="child" class="flex-center">
            child
            <span id="son" class="flex-center">
                <a id="aTag" href="https://baidu.com">点我啊</a>
            </span>
        </p>
    </div>
</body>
<script type="text/javascript">
    const parent = document.getElementById('parent');
const child = document.getElementById('child');
const son = document.getElementById('son');
const aTag = document.getElementById('aTag');
 
aTag.addEventListener('click', function(e) {
    e.preventDefault();    // 阻止A标签默认事件
})
 
window.addEventListener('click', function(e) {
    // e.target.nodeName 和 e.currentTarget.nodeName 下面会讲这两个参数的意思
    console.log('window 捕获', e.target.nodeName, e.currentTarget.nodeName);
}, true); 
// addEventListener第三个参数 true代表在捕获阶段执行。false或者不填代表在冒泡阶段执行。
 
parent.addEventListener('click', function(e) {
    console.log('parent 捕获', e.target.nodeName, e.currentTarget.nodeName);
}, true);
 
child.addEventListener('click', function(e) {
    console.log('child 捕获', e.target.nodeName, e.currentTarget.nodeName);
}, true);
 
son.addEventListener('click', function(e) {
    console.log('son 捕获', e.target.nodeName, e.currentTarget.nodeName);
}, true);
 
son.addEventListener('click', function(e) {
    console.log('son 冒泡', e.target.nodeName, e.currentTarget.nodeName);
}, false);
 
child.addEventListener('click', function(e) {
    console.log('child 冒泡', e.target.nodeName, e.currentTarget.nodeName);
}, false);
 
parent.addEventListener('click', function(e) {
    console.log('parent 冒泡', e.target.nodeName, e.currentTarget.nodeName);
}, false);
 
window.addEventListener('click', function(e) {
    console.log('window 冒泡', e.target.nodeName, e.currentTarget.nodeName);
}, false);
</script>

点击span标签时控制台输出信息如下:

3. addEventListener的第三个参数

        addEventListener方法用来为一个特定的元素绑定一个事件处理函数,是JavaScript中的常用方法。addEventListener有三个参数:

 element.addEventListener(event, function, useCapture)

addEventListener第三个参数

  • 默认值为冒泡
  • true:代表在捕获阶段执行
  • false或者不填:代表在冒泡阶段执行

4. 事件委托阶段案例

4.1 事件冒泡案例

(第三个参数是false的时候是冒泡)

var body=document.getElementsByTagName('body')[0];
 
window.addEventListener('click',function(){
        console.log('window')
},false)
 body.addEventListener('click',function(){
        console.log('body')
},false)
 
var oDiv=document.getElementsByTagName('div')[0];
 oDiv.addEventListener('click',function(){
    console.log(1)
 },false)
 
oDiv.addEventListener('click',function(){
  console.log(2)
},false)点击div运行结果
 

点击div运行结果

 4.2 事件捕获案例

var body=document.getElementsByTagName('body')[0];
 
window.addEventListener('click',function(){
        console.log('window')
},true)
 body.addEventListener('click',function(){
        console.log('body')
},true)
 
var oDiv=document.getElementsByTagName('div')[0];
 oDiv.addEventListener('click',function(){
    console.log(1)
 },true)
 
oDiv.addEventListener('click',function(){
  console.log(2)
},true)

点击div运行结果

事件捕获就是从上往下一级一级往下找,先找父级在找子级

 5. 经典面试题 

 6. 事件代理总结

        使用事件代理的好处不仅在于将多个事件处理函数减为一个,而且对于不同的元素可以有不同的处理方法。假如上述列表元素当中添加了其他的元素节点(如:a、span等),我们不必再一次循环给每一个元素绑定事件,直接修改事件代理的事件处理函数即可。

6.1 冒泡还是捕获?

        对于事件代理来说,在事件捕获或者事件冒泡阶段处理并没有明显的优劣之分,但是由于事件冒泡的事件流模型被所有主流的浏览器兼容,从兼容性角度来说还是建议大家使用事件冒泡模型。

6.2 阻止事件冒泡

1. 给子级加 event.stopPropagation( )

$("#div1").mousedown(function(e){
    var e=event||window.event;
    event.stopPropagation();
});

2. 在事件处理函数中返回 false

$("#div1").mousedown(function(event){
    var e=e||window.event;
    return false;
});

但是这两种方式是有区别的。return false 不仅阻止了事件往上冒泡,而且阻止了事件本身(默认事件)。event.stopPropagation()则只阻止事件往上冒泡,不阻止事件本身。

3.  event.target==event.currentTarget,让触发事件的元素等于绑定事件的元素,也可以阻止事件冒泡;

6.4 阻止默认事件

(1)event.preventDefault( )

(2)return false

什么叫做默认行为

比如点击a标签会跳转到另个页面,比如拖拽到一张图片到浏览器,浏览器会打开这个图片,比如点击表单提交按钮,会提交当前表单……

如果我们不希望这些默认行为的发生,我们应该怎么做?

最开始写的代码中有个a标签,点击时是要跳转到百度的

<a id="aTag" href="https://baidu.com">点我啊</a>

如果我们不想让他跳转到百度,在a标签事件上做个拦截,当点击 a标签时,就不会跳转到百度。

const aTag = document.getElementById('aTag');
 
aTag.addEventListener('click', function(e) {
    e.preventDefault();    // 阻止a标签默认事件
})
 

相关文章:

  • R语言和医学统计学(8):logistic回归
  • MATLAB | 绘图复刻(三) | 分层聚类分析图:树状图+热图
  • 大学生计算机相关专业有什么血泪建议吗?
  • 不愧是阿里面试官整理的java高级工程师面试 1000 题,面面俱到,太全了
  • 【开卷数据结构 】指针的初步认识
  • Python高级_第3章_HTTP协议与静态Web服务器开发
  • 创造一个表格编辑距离指标
  • 大数据Hadoop之——Apache Hudi 数据湖实战操作(FlinkCDC)
  • ikun网站成名录: HTML 中的常用标签用法,从0到1创建一个ikun简介
  • <Linux系统复习>文件描述符
  • 【C++入门】(纯)虚函数和多态、抽象类、接口
  • 推荐一个C#开发的窗口扩展菜单,支持系统所以窗口
  • 初识深度学习-吴恩达
  • Rust Tauri OpenCV 写一个桌面摄像头
  • 在python中使用ggplot2
  • docker-consul
  • Netty源码解析1-Buffer
  • QQ浏览器x5内核的兼容性问题
  • React中的“虫洞”——Context
  • Spark in action on Kubernetes - Playground搭建与架构浅析
  • TypeScript迭代器
  • 测试如何在敏捷团队中工作?
  • 机器学习 vs. 深度学习
  • 经典排序算法及其 Java 实现
  • 跨域
  • 离散点最小(凸)包围边界查找
  • 入口文件开始,分析Vue源码实现
  • 问:在指定的JSON数据中(最外层是数组)根据指定条件拿到匹配到的结果
  • 协程
  • 中国人寿如何基于容器搭建金融PaaS云平台
  • postgresql行列转换函数
  • ​无人机石油管道巡检方案新亮点:灵活准确又高效
  • #gStore-weekly | gStore最新版本1.0之三角形计数函数的使用
  • #Js篇:单线程模式同步任务异步任务任务队列事件循环setTimeout() setInterval()
  • #大学#套接字
  • #前后端分离# 头条发布系统
  • (14)目标检测_SSD训练代码基于pytorch搭建代码
  • (32位汇编 五)mov/add/sub/and/or/xor/not
  • (Pytorch框架)神经网络输出维度调试,做出我们自己的网络来!!(详细教程~)
  • (编译到47%失败)to be deleted
  • (含react-draggable库以及相关BUG如何解决)固定在左上方某盒子内(如按钮)添加可拖动功能,使用react hook语法实现
  • (每日持续更新)jdk api之StringBufferInputStream基础、应用、实战
  • (南京观海微电子)——I3C协议介绍
  • (欧拉)openEuler系统添加网卡文件配置流程、(欧拉)openEuler系统手动配置ipv6地址流程、(欧拉)openEuler系统网络管理说明
  • (转)全文检索技术学习(三)——Lucene支持中文分词
  • ***php进行支付宝开发中return_url和notify_url的区别分析
  • 、写入Shellcode到注册表上线
  • .NET / MSBuild 扩展编译时什么时候用 BeforeTargets / AfterTargets 什么时候用 DependsOnTargets?
  • .NET 8.0 中有哪些新的变化?
  • .net core 微服务_.NET Core 3.0中用 Code-First 方式创建 gRPC 服务与客户端
  • .net framework 4.0中如何 输出 form 的name属性。
  • .NET 设计一套高性能的弱事件机制
  • .NET/C# 中你可以在代码中写多个 Main 函数,然后按需要随时切换
  • .php文件都打不开,打不开php文件怎么办
  • @JsonFormat与@DateTimeFormat注解的使用