浅谈js的事件机制,事件冒泡、事件捕获、事件代理(事件委托)

引言

简单来说,当用户或者你操作网页的DOM时(鼠标点击、鼠标移动等),会触发一些事件,为处理用户的交互,而做出一些响应。因此你需要了解事件的传播方式(捕获、冒泡)和使用事件委托来提高性能。

事件简述

JavaScript中,事件是以事件流的形式存在的,事件流的顺序分为:捕获和冒泡。阶段是:捕获、模板、冒泡。简单来说:

  • 事件的顺序
    • 捕获:外 > 内
    • 冒泡:内 > 外
  • 事件的阶段
    • 1.捕获阶段
    • 2.目标阶段
    • 3.冒泡阶段

JavaScript中,可以使用addEventListener方法,来为元素绑定事件。

js 复制代码
element.addEventListener(event, handler, useCapture);
  • event 绑定的事件类型
  • handler 事件触发时要执行的函数
  • useCapture 事件处理的阶段,可选值。false(默认,冒泡阶段) true(捕获阶段)

事件冒泡

先讲讲事件冒泡,因为addEventListener的默认处理时机就是冒泡阶段。也就是说,如果有3个盒子逐层嵌套(box1蓝色 box2绿色 box3粉色),当你点击了粉色时,那么会打印的是:box3粉色、box2绿色、box1蓝色(由内到外)。

html 复制代码
<div class="box1">
    <div class="box2">
        <div class="box3"></div>
    </div>
</div>
js 复制代码
const ebox1 = document.querySelector('.box1')
const ebox2 = document.querySelector('.box2')
const ebox3 = document.querySelector('.box3')

ebox1.addEventListener('click', function() {
  console.log('box1蓝色')
})

ebox2.addEventListener('click', function() {
  console.log('box2绿色')
})

ebox3.addEventListener('click', function() {
  console.log('box3粉色')
})

那么,如果改变事件的处理顺序,为蓝色粉色盒子添加,第三个参数为true,代码如下。当你再次点击粉色盒子,猜猜看打印的顺序是什么?
点击查看答案 box1蓝色、box3粉色、box2绿色

js 复制代码
...

ebox1.addEventListener('click', function() {
  console.log('box1蓝色')
}, true)

ebox2.addEventListener('click', function() {
  console.log('box2绿色')
})

ebox3.addEventListener('click', function() {
  console.log('box3粉色')
}, true)

因为添加了第三个参数,改变了其处理的顺序。分析一下,原先的触发顺序是由内到外,现在box1box3的处理顺序被变为了由外到内。所以当你点击box3粉色盒子,box1蓝色在最外面,会先打印;其次再打印box3粉色,最后再打印box2绿色

  • box3粉色,捕获,外>内
  • box2绿色,冒泡,内>外
  • box1蓝色,捕获,外>内

事件捕获

事件捕获,其触发的顺序与事件冒泡的相反,在上面已经举过类似的例子,就不再赘述。

阻止事件传播

阻止事件传播,常用的有两种方法:

  • event.stopPropagation() 阻止事件传播,不会阻止同一元素上的其他的事件处理程。
  • event.stopImmediatePropagation() 阻止事件传播,阻止同一元素上的其他的事件处理程。

event.stopPropagation()

如果一个元素上绑定了多个事件处理程序,调用event.stopPropagation()方法只会阻止事件往高级元素传播,而不会阻止同一元素上的其他事件处理程序被触发。

假设你为box3粉色盒子添加了event.stopPropagation()方法,猜猜打印的是什么?
点击查看答案 box1蓝色、box3粉色、box3粉色 222 点击查看分析 因为,box1蓝色,box3粉色,是捕获阶段触发(外>内);box2绿色是冒泡阶段触发(内>外);当你点击粉色的时候,首先会触发蓝色,然后再触发粉色(绑定了两个事件,就会打印两条数据)。至于绿色不触发,是由于为粉色添加了阻止事件传播,所以不会向外层继续传播事件,也就是说绿色的是不会触发的。

js 复制代码
...

ebox1.addEventListener('click', function() {
  console.log('box1蓝色')
}, true)

ebox2.addEventListener('click', function() {
  console.log('box2绿色')
})

ebox3.addEventListener('click', function(event) {
  console.log('box3粉色')
  event.stopPropagation()
}, true)

ebox3.addEventListener('click', function(event) {
  console.log('box3粉色 222')
}, true)

event.stopImmediatePropagation()

如果一个元素上绑定了多个事件处理程序,调用event.stopImmediatePropagation()方法会立即停止事件传播,并且不会触发同一元素上的其他事件处理程序。
点击查看答案 box1蓝色、box3粉色 点击查看分析 因为,box1蓝色,box3粉色,是捕获阶段触发(外>内);box2绿色是冒泡阶段触发(内>外);当你点击粉色的时候,首先会触发蓝色,然后再触发粉色(虽然绑定了两个事件,但是由于使用了event.stopImmediatePropagation()方法,所以就不会触发粉色盒子的其他事件了)。至于绿色不触发,是由于为粉色添加了阻止事件传播,所以不会向外层继续传播事件,也就是说绿色的是不会触发的。

js 复制代码
...

ebox1.addEventListener('click', function() {
  console.log('box1蓝色')
}, true)

ebox2.addEventListener('click', function() {
  console.log('box2绿色')
})

ebox3.addEventListener('click', function(event) {
  console.log('box3粉色')
  event.stopImmediatePropagation()
}, true)

ebox3.addEventListener('click', function(event) {
  console.log('box3粉色 222')
}, true)

事件代理(事件委托)

从字面意思理解,委托就是把事情交给别人处理。在JavaScript中,事件委托就是把子元素的事件交给父元素处理。

举个例子

现在要为每一个li添加一个事件,假设li有100个,你就需要为每一个li添加一个事件,这样会占用100个内存。因此,如果使用事件委托的话,可以利用事件的冒泡机制,为ul绑定一个事件,那么点击任意一个li的时候,都会将事件触发到父元素ul上。

html 复制代码
<ul>
    <li>1</li>
    <li>2</li>
    <li>3</li>
    ...
</ul>

代码展示

html 复制代码
<ul id="ul">
    <li>1</li>
    <li>2</li>
    <li>3</li>
    ...
</ul>

可以使用event.target获取到点击的元素,event.target.innerHTML获取到点击的元素的内容。 如下代码,当你点击li时,会添加一个红色的背景,再次点击,会将背景变为白色。

js 复制代码
const ulE = document.querySelector('#ul')

ulE.addEventListener('click', function (event) {
  console.log('ulE event', event.target, event.target.innerHTML)
  const target = event.target
  if (target.style.backgroundColor !== 'red') {
    target.style.backgroundColor = 'red'
  } else {
    target.style.backgroundColor = '#fff'
  }
})

因此,使用事件代理/委托可以提高性能,减少注册的事件。

附录

常见的事件

事件 触发事件的情况
click 单击鼠标左键时
mousemove 鼠标光标移动时
mouseover 鼠标光标移动到某个元素上时,类似 CSS 的 hover
mouseout 鼠标光标移动出元素的边界时
dblclick 双击鼠标左键时
DOMContentLoaded DOM内容完全加载时
keydown 按下键盘上的一个键时
keyup 释放键盘上的一个键时
submit 表单被提交时

参考文献

相关推荐
10年前端老司机2 小时前
10道js经典面试题助你找到好工作
前端·javascript
codingandsleeping8 小时前
重读《你不知道的JavaScript》(上)- 作用域和闭包
前端·javascript
前端风云志9 小时前
TypeScript实用类型之Omit
javascript
烛阴10 小时前
Puppeteer入门指南:掌控浏览器,开启自动化新时代
前端·javascript
芝士加12 小时前
Playwright vs MidScene:自动化工具“双雄”谁更适合你?
前端·javascript
Carlos_sam13 小时前
OpenLayers:封装一个自定义罗盘控件
前端·javascript
前端南玖13 小时前
深入Vue3响应式:手写实现reactive与ref
前端·javascript·vue.js
Yueyanc14 小时前
LobeHub桌面应用的IPC通信方案解析
前端·javascript
麦当_15 小时前
基于 Shadcn 的可配置表单解决方案
前端·javascript·面试
Cutey91615 小时前
使用Canvas实现实时视频处理:从黑白滤镜到高级特效
前端·javascript