引言
简单来说,当用户或者你操作网页的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)
因为添加了第三个参数,改变了其处理的顺序。分析一下,原先的触发顺序是由内到外,现在box1与box3的处理顺序被变为了由外到内。所以当你点击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 | 表单被提交时 |