在讨论冒泡和捕获之前,先看这么一段代码:

<style>
.bd {
border: 1px solid #000;
padding: 8px;
}
</style> <div id="container1" class="bd">
外层
<div id="container2" class="bd">
内层
<div id="container3" class="bd">
最内层
<div id="container4" class="bd">
按钮
</div>
</div>
</div>
</div> <script>
(() => {
const container1 = document.querySelector('#container1')
const container2 = document.querySelector('#container2')
const container3 = document.querySelector('#container3')
const container4 = document.querySelector('#container4')
container1.addEventListener('click', () => {
console.log('container1')
})
container2.addEventListener('click', () => {
console.log('container2')
})
container3.addEventListener('click', () => {
console.log('container3')
})
container4.addEventListener('click', () => {
console.log('container4')
})
})()
</script>

页面渲染大概长这样:

点击最里面的 按钮 元素,按照思维惯例,是否应该先执行 container1 的点击事件??毕竟 container1 是最外层,而且也是最先绑定事件的元素。

然而控制台输出结果为:

container4
container3
container2
container1

有点出乎意料是吧,为什么先执行的是 container4 呢?

事件冒泡

JS 绑定的事件默认是冒泡规则,什么意思呢?可以理解为:触发事件后就像水里面有一个泡泡,在水底慢慢的往上冒,从触发事件的目标元素开始,经过一层一层的盒模型,分别触发盒模型身上绑定的事件。

所以上面代码中,在点击 按钮 时,先触发了本身绑定的 click 事件,再一层一层往外传播,最终就打印出了控制台输出的结果。

事件捕获

注意:仅默认状态下,事件是冒泡规则,在绑定事件时候,可以修改第三个配置参数改为由外向内传播,这种传播顺序就是 事件捕获

以上面代码为蓝本,仅添加 addEventListener 的第三个参数为 true,就将绑定规则改为了 事件捕获 。如下:

container1.addEventListener('click', () => {
console.log('container1')
}, true)
container2.addEventListener('click', () => {
console.log('container2')
}, true)
container3.addEventListener('click', () => {
console.log('container3')
}, true)
container4.addEventListener('click', () => {
console.log('container4')
}, true)

还是点击 按钮,上面代码执行结果:

container1
container2
container3
container4

事件捕获还有另一种写法,第三个参数可以传入一个对象,通过对象的 capture 属性设置为事件捕获。

container1.addEventListener('click', () => {
console.log('container1')
}, {
// 另一种设置事件捕获方式
capture: true,
})

冒泡与捕获顺序

既然同一个事件有冒泡与捕获,那么冒泡与捕获的顺序如何?上例子:

container1.addEventListener('click', () => {
console.log('冒泡:', 'container1')
})
container2.addEventListener('click', () => {
console.log('冒泡:', 'container2')
})
container3.addEventListener('click', () => {
console.log('冒泡:', 'container3')
})
container4.addEventListener('click', () => {
console.log('冒泡:', 'container4')
})
container1.addEventListener('click', () => {
console.log('捕获:', 'container1')
}, true)
container2.addEventListener('click', () => {
console.log('捕获:', 'container2')
}, true)
container3.addEventListener('click', () => {
console.log('捕获:', 'container3')
}, true)
container4.addEventListener('click', () => {
console.log('捕获:', 'container4')
}, true)

同时给元素绑定两种事件,点击 按钮 执行结果:

捕获: container1
捕获: container2
捕获: container3
捕获: container4
冒泡: container4
冒泡: container3
冒泡: container2
冒泡: container1

到这里已经可以得出结论:JS 的事件传播会经历三个阶段,由 事件捕获 开始,传递到 目标元素 之后,就改为 事件冒泡,冒泡阶段完了之后事件结束。

阻止事件传播

既然事件有传播,那程序就有办法阻止事件传播。所有事件执行时都有一个 event 对象,此对象中有方法可用于阻止事件传播。

示例:

container1.addEventListener('click', () => {
console.log('冒泡:', 'container1')
})
container2.addEventListener('click', () => {
console.log('冒泡:', 'container2')
})
container3.addEventListener('click', () => {
console.log('冒泡:', 'container3')
})
container4.addEventListener('click', () => {
console.log('冒泡:', 'container4')
})
container1.addEventListener('click', (event) => {
event.stopPropagation()
console.log('捕获:', 'container1')
}, true)
container2.addEventListener('click', () => {
console.log('捕获:', 'container2')
}, true)
container3.addEventListener('click', () => {
console.log('捕获:', 'container3')
}, true)
container4.addEventListener('click', () => {
console.log('捕获:', 'container4')
}, true)

注意 event.stopPropagation() 这个方法,此方法是阻止事件传播的关键。

以上代码在 container1 这个元素上就阻止了事件传播,所以点击 按钮 之后,仅 container1 会执行,其他所有元素都不会再触发,结果:

捕获: container1

调用 event.stopPropagation() 就是告诉 JS,事件到此为止,不再继续了。


event 对象其他常用方法和属性:

event.target:触发事件的原始元素。

event.currentTarget:当前绑定事件的元素(等同于 this)。

event.type:事件类型(如 "click")。

event.preventDefault():阻止默认行为(如表单提交、链接跳转、自定义右键菜单)。

event.stopPropagation():阻止事件冒泡。

event.stopImmediatePropagation():阻止同一元素的其他监听器执行。

event.x 和 event.y:鼠标点击位置的坐标。


在事件中要使用 this 获取元素时,必须使用 function 函数,使用箭头函数绑定的事件 this 将会指向外层作用域的 this 指针,如下代码中箭头函数 this 指向的是 Window

<div id="container4" class="bd">
按钮
</div> <script>
(() => {
const container4 = document.querySelector('#container4')
container4.addEventListener('click', () => {
console.log(this) // Window 对象
})
container4.addEventListener('click', function () {
console.log(this) // div#container4
})
})()
</script>

写在最后

编程中的细节问题,总是越挖掘越心惊,学得越来越多,才会发现知道的越来越少。

Web前端入门第 74 问:JavaScript 事件冒泡与事件捕获的更多相关文章

  1. web前端入坑第五篇:秒懂Vuejs、Angular、React原理和前端发展历史

    秒懂Vuejs.Angular.React原理和前端发展历史 2017-04-07 小北哥哥 前端你别闹 今天来说说 "前端发展历史和框架" 「前端程序发展的历史」 「 不学自知, ...

  2. javascript中的事件冒泡、事件捕获和事件执行顺序

    谈起JavaScript的 事件,事件冒泡.事件捕获.阻止默认事件这三个话题,无论是面试还是在平时的工作中,都很难避免. DOM事件标准定义了两种事件流,这两种事件流有着显著的不同并且可能对你的应用有 ...

  3. javascript的事件冒泡,阻止事件冒泡和事件委托, 事件委托是事件冒泡的一个应用。

    首先,弄明白js 当中,什么是事件,事件模型在js中是如何设计的.什么是事件冒泡? 什么是“事件冒泡”呢?假设这里有一杯水,水被用某种神奇的方式分成不同颜色的几层.这时,从最底层冒出了一个气泡,气泡会 ...

  4. JavaScript 进阶教程一 JavaScript 中的事件流 - 事件冒泡和事件捕获

    先看下面的示例代码: <html xmlns="http://www.w3.org/1999/xhtml"> <head> <title>Jav ...

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

    JavaScript事件冒泡和事件委托 付建宇 - 2 条评论 接触JavaScript不久,学的东西也不是特别多.小雨就是习惯把平时学到的东西拿出来分享.一方面加强自己的印象,一方面可以让自己的经验 ...

  6. 【转载】浅谈事件冒泡与事件捕获 - javascript 事件代理

    原文:https://segmentfault.com/a/1190000000749838 事件冒泡与事件捕获 事件冒泡和事件捕获分别由微软和网景公司提出,这两个概念都是为了解决页面中事件流(事件发 ...

  7. JavaScript中的事件冒泡?事件传播的解释

    注:本文来源  可译网 事件冒泡是你在学习javaScript旅途中遇到的一个术语,它涉及到当一个元素被另一个元素嵌套时调用事件处理的顺序,并且两个元素注册了同一个事件(例如,点击事件). 但是事件冒 ...

  8. JavaScript(3)---事件冒泡、事件捕获

    JavaScript(3)---事件冒泡与事件捕获 一.理解冒泡与捕获 假设有这么一段代码 <body> <div><p>标签</p> </div ...

  9. javascript --- 事件冒泡与事件捕获

    事件冒泡与事件捕获 事件冒泡和事件捕获分别由微软和网景公司提出,这两个概念都是为了解决页面中事件流(事件发生顺序)的问题.考虑下面这段代码,就不写html->head,body之类的代码了,自行 ...

  10. javascript 事件冒泡和事件代理

    事件冒泡 简单的讲,当子元素的事件处理函数被触发(如onclick),该事件会从事件源(当前子元素)逐级向上层元素传递,触发祖先元素的 onclik 事件,一直到最外层 html 根元素. 这可能会带 ...

随机推荐

  1. .NET周刊【3月第3期 2025-03-16】

    国内文章 在 VisualStudio 一键 F5 启动调试 Roslyn 分析器项目 https://www.cnblogs.com/lindexi/p/18730521 本文将告诉大家如何在 Vi ...

  2. tomghost打靶学习笔记(3)

    主要内容 信息收集:ajp漏洞 横向提权:在没有办法立刻提升到管理员权限时,可以试试通过横向的权限提升切换到其他用户再做提权尝试 涉及尝试了前两台靶机没有用过的枚举方法,比如SUID 使用john解码 ...

  3. c数组与结构体

    数组,存储同类型的复合类型:结构体,存储不同类型的复合类型,用于自定义数据结构. 计算机中,针对存储大量数据的集合,有着两种方式,一种是以块式集中存储数据,这就是数组的存储方式,大量同类型的数据集中放 ...

  4. JMeter提取响应结果保存到本地总结

    1.说明 本次实验以登录接口为例提取响应结果中的uid和ticket参数并保存到csv文件 2.脚本结构 说明: 1)本次实验的在于BeanShell后置处理程序的编写,登录接口参数传递之前有些总结, ...

  5. spring的控制反转(IoC)

    ioc的作用: 削减计算机程序的耦合(解除我们代码中的依赖关系 解耦的思路: 第一步:使用反射来创建对象,而避免使用new关键字. 第二步:通过读取配置文件来获取要创建的对象全限定类名

  6. Robot Framework原生库的编辑与应用

    RF有一些操作指令不存在,需要自己添加方法,比如selenium里有click_and_hold指令(鼠标保持点击状态)而RF内没有.所以需要在库文件里加入这个方法 C:\Python27\Lib\s ...

  7. @EnableAspectJAutoProxy

    开启动态代理配置 官方文档 通过xml配置 <aop:aspectj-autoproxy proxy-target-class="true"/> 通过注解配置 @Ena ...

  8. 探秘Transformer系列之(31)--- Medusa

    探秘Transformer系列之(31)--- Medusa 目录 探秘Transformer系列之(31)--- Medusa 0x00 概述 0x01 原理 1.1 动机 1.2 借鉴 1.3 思 ...

  9. Linux 常识和操作(常用命令)

    1. 存放用户账号的文件在哪里? /etc/passwd 2. 如何删除一个非空的目录? rm -rf 目录名 3. 查看当前的工作目录用什么命令? pwd 4. 创建一个文件夹用什么命令? mkdi ...

  10. 获取传入值的上一个月【月初】和【月末】【yyyy-MM-dd】

    获取传入值的上一个月[月初]和[月末] 常量值:String DATE_FORMAT_YYYY_MM_DD = "yyyy-MM-dd"; // 获取传入值的上一个月月初 : fo ...