彻底弄懂 js 中事件冒泡和捕获🎈
事件触发经历的三个阶段
- 首先我们要弄清楚当一个dom事件被触发时,它不仅仅是单纯地在自身对象上触发一次,而是经历了三个不同的阶段:👇
- 捕获阶段:事件对象从
window
开始依次向下传递,直到目标的父级元素,从外向内捕获事件对象; - 目标阶段:到达目标事件位置,触发事件;
- 冒泡阶段:从目标的父级开始依次向上传递,直到
window
停止,从内向外冒泡事件对象。
- 下面是 w3c解释事件流 的图:
冒泡和捕获
当我们注册一个事件时,事件默认使用冒泡事件流,不使用捕获事件流。
1
2
3addEventListener(type, listener);
addEventListener(type, listener, useCapture);
addEventListener(type, listener, options);event: 必须。字符串,指定 事件类型。
listener: 必须。一个实现了EventListener
接口的对象,或者是一个事件处理函数。
useCapture:可选。布尔值,指定事件是否在捕获或冒泡阶段执行。(默认false
)options: 可选,一个对象,可用属性如下👇
- capture:同 useCapture
- once:布尔值,表示
listener
被添加后最多只调用一次。如果为true
,listener
会在其被调用之后自动移除。 - passive:布尔值,为
true
时,表示listener
永远不会调用preventDefault()
;详细请查看 使用 passive 改善滚屏性能。
下面在代码中验证,直接附上全部代码。(可以粘到自己编辑器中运行、尝试一下)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57<html lang="en">
<head>
<meta charset="UTF-8">
<title>event</title>
<style>
#one {
width: 600px;
height: 600px;
background-color: green;
}
#two {
width: 400px;
height: 400px;
background-color: yellow;
}
#three {
width: 200px;
height: 200px;
background-color: deepskyblue;
}
</style>
</head>
<body>
<div id="one">one
<div id="two">two
<div id="three">three</div>
</div>
</div>
<script>
var one = document.getElementById('one')
var two = document.getElementById('two')
var three = document.getElementById('three')
one.addEventListener('click', function () {
console.log('one捕获')
}, true)
two.addEventListener('click', function () {
console.log('two捕获')
}, true)
three.addEventListener('click', function () {
console.log('three捕获')
}, true)
one.addEventListener('click', function () {
console.log('one冒泡')
}, false)
two.addEventListener('click', function () {
console.log('two冒泡')
}, false)
three.addEventListener('click', function () {
console.log('three冒泡')
}, false)
</script>
</body>
</html>当我们点击three时,可以看到确实是先由外向内事件捕获,一直到事发元素,再由内向外冒泡到根节点上。
如果一个元素既注册了冒泡事件,也注册了捕获事件,则按照注册顺序执行。
我们修改代码把冒泡事件放在捕获事件之前:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19one.addEventListener('click', function(){
console.log('one冒泡')
}, false)
two.addEventListener('click', function(){
console.log('two冒泡')
}, false)
three.addEventListener('click', function(){
console.log('three冒泡')
}, false)
one.addEventListener('click', function(){
console.log('one捕获')
}, true)
two.addEventListener('click', function(){
console.log('two捕获')
}, true)
three.addEventListener('click', function(){
console.log('three捕获')
}, true)再点击three,可以看到这次three先是先冒泡后捕获的,由此可见一个元素同时注册了冒泡和捕获事件,则会按照注册顺序执行。
阻止事件冒泡
在很多时候我们并不需要元素绑定的事件向外冒泡,这时我们就要阻止事件的冒泡。
我们再次修改代码,阻止three的事件冒泡:1
2
3
4
5three.addEventListener('click', function(event){
console.log('three冒泡')
// 阻止冒泡
e.stopPropagation()
}, false)修改完代码后我们再次点击three,可以看到three的点击事件触发后就停止继续向外冒泡了;
事件委托
了解了JavaScript中事件的冒泡和捕获后,我就可以利用js的这种机制完成一些代码上的优化了。
例如现在有这样的一段代码👇
1
2
3
4
5
6
7<ul id="list">
<li>item 1</li>
<li>item 2</li>
<li>item 3</li>
<li>item 4</li>
......
</ul>我们需要给一个有很多子元素的列表中的每一个
li
绑定一个事件,如果我们直接手动给每个li
绑定事件,不仅非常的麻烦🤮,而且也会造成很大的资源浪费。这个时候我们就可以利用事件冒泡机制将监听事件只绑定在父元素
ul
上面,当事件触发时再判断触发事件的目标元素,来完成我们的操作。1
2
3
4
5
6
7
8var list = document.getElementById('list')
list.addEventListener('click', function(e) {
var target = event.target
// 判断是不是目标元素
if(target.nodeName.toLowerCase() === 'li') {
console.log(target.innerHTML)
}
})这样当
#list
下的li
被点击后我们就可以获取到被点击的li
完成我们的操作了🤗。
本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 zxyong!
评论