JavaScript 事件冒泡与事件捕获

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

xml 复制代码
<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,就将绑定规则改为了 事件捕获 。如下:

javascript 复制代码
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 属性设置为事件捕获。

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

冒泡与捕获顺序

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

javascript 复制代码
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 对象,此对象中有方法可用于阻止事件传播。

示例:

javascript 复制代码
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

xml 复制代码
<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>

写在最后

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

相关推荐
芬兰y13 分钟前
VUE 带有搜索功能的穿梭框(简单demo)
前端·javascript·vue.js
好果不榨汁20 分钟前
qiankun 路由选择不同模式如何书写不同的配置
前端·vue.js
小蜜蜂dry20 分钟前
Fetch 笔记
前端·javascript
拾光拾趣录21 分钟前
列表分页中的快速翻页竞态问题
前端·javascript
小old弟22 分钟前
vue3,你看setup设计详解,也是个人才
前端
Lefan26 分钟前
一文了解什么是Dart
前端·flutter·dart
Patrick_Wilson31 分钟前
青苔漫染待客迟
前端·设计模式·架构
vvilkim34 分钟前
Nuxt.js 全面测试指南:从单元测试到E2E测试
开发语言·javascript·ecmascript
写不出来就跑路1 小时前
基于 Vue 3 的智能聊天界面实现:从 UI 到流式响应全解析
前端·vue.js·ui
OpenTiny社区1 小时前
盘点字体性能优化方案
前端·javascript