引言
简单来说,当用户或者你操作网页的DOM
时(鼠标点击、鼠标移动等),会触发一些事件,为处理用户的交互,而做出一些响应。因此你需要了解事件的传播方式(捕获、冒泡)和使用事件委托来提高性能。
事件简述
在JavaScript
中,事件是以事件流
的形式存在的,事件流
的顺序分为:捕获和冒泡。阶段是:捕获、模板、冒泡。简单来说:
- 事件的顺序
- 捕获:外 > 内
- 冒泡:内 > 外
- 事件的阶段
- 1.捕获阶段
- 2.目标阶段
- 3.冒泡阶段
添加事件监听器
在JavaScript
中,可以使用addEventListener
方法,来为元素绑定事件。
element.addEventListener(event, handler, useCapture);
event
绑定的事件类型handler
事件触发时要执行的函数useCapture
事件处理的阶段,可选值。false(默认,冒泡阶段) true(捕获阶段)
移除事件监听器
要移除添加在元素上的事件监听器,需要调用removeEventListener()
方法,并传递事件的类型event
和之前添加的方法的handler
,如下所示:
element.removeEventListener(eventType, handlerName);
eventType
绑定的事件类型handlerName
之前添加的事件触发时要执行的函数名
如果没有写函数名,就无法移除事件监听器。
事件冒泡
先讲讲事件冒泡,因为addEventListener
的默认处理时机就是冒泡阶段
。也就是说,如果有3个盒子逐层嵌套(box1蓝色 box2绿色 box3粉色),当你点击了粉色
时,那么会打印的是:box3粉色、box2绿色、box1蓝色(由内到外)。
<div class="box1">
<div class="box2">
<div class="box3"></div>
</div>
</div>
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
,代码如下。当你再次点击粉色
盒子,猜猜看打印的顺序是什么?
...
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()
方法,猜猜打印的是什么?
...
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()
方法会立即停止事件传播,并且不会触发同一元素上的其他事件处理程序。
...
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
上。
<ul>
<li>1</li>
<li>2</li>
<li>3</li>
...
</ul>
代码展示
<ul id="ul">
<li>1</li>
<li>2</li>
<li>3</li>
...
</ul>
可以使用event.target
获取到点击的元素,event.target.innerHTML
获取到点击的元素的内容。
如下代码,当你点击li
时,会添加一个红色的背景,再次点击,会将背景变为白色。
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'
}
})
因此,使用事件代理/委托
可以提高性能,减少注册的事件。
附录
常见的事件
-
键盘事件
keydown
按下键盘上的一个键时keyup
释放键盘上的一个键时
-
鼠标事件
click
单击事件dblclick
双击事件mousedown
鼠标按钮被按下mouseup
鼠标按钮被释放mousemove
鼠标光标在元素上移动时mouseover
鼠标光标移动到某个元素上时,类似 CSS 的 hovermouseout
鼠标光标移出元素边界时contextmenu
打开上下文菜单时,例如单击鼠标右键时
-
其他事件
submit
表单被提交时DOMContentLoaded
DOM内容完全加载时
参考文献
- # JS中的事件冒泡、事件捕获、事件委托
- # JS事件机制浅析:事件捕获、事件冒泡和事件委托
- # 理解 DOM 事件和 JavaScript 事件监听器
- # 深入理解 JavaScript 事件流机制:冒泡、捕获及事件代理