浅谈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 表单被提交时

参考文献

相关推荐
gnip15 分钟前
项目开发流程之技术调用流程
前端·javascript
答案—answer15 分钟前
three.js编辑器2.0版本
javascript·three.js·three.js 编辑器·three.js性能优化·three.js模型编辑·three.js 粒子特效·three.js加载模型
gnip44 分钟前
SSE技术介绍
前端·javascript
yinke小琪1 小时前
JavaScript DOM节点操作(增删改)常用方法
前端·javascript
爱编程的喵1 小时前
从XMLHttpRequest到Fetch:前端异步请求的演进之路
前端·javascript
豆苗学前端1 小时前
手把手实现支持百万级数据量、高可用和可扩展性的穿梭框组件
前端·javascript·面试
yinke小琪1 小时前
JavaScript 事件冒泡与事件捕获
前端·javascript
gzzeason1 小时前
Ajax:现代JS发起http通信的代名词
前端·javascript·ajax
iphone1082 小时前
一次编码,多端运行:HTML5多终端调用
前端·javascript·html·html5
iccb10132 小时前
我是如何实现在线客服系统的极致稳定性与安全性的
前端·javascript·后端