触发一个事件的过程有三个阶段:
- 捕获阶段(capture phase)
- 目标阶段(target phase)
- 冒泡阶段(bubbling phase)
https://www.w3.org/TR/DOM-Level-3-Events/#event-flow
![](https://img.haomeiwen.com/i6631354/a1e54eee088a5832.png)
举个具体的例子:
![](https://img.haomeiwen.com/i6631354/39f6a864cfe60649.png)
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<style>
#a {
width: 200px;
height: 200px;
background-color: coral;
}
#b {
width: 100px;
height: 100px;
background-color: cornflowerblue;
}
</style>
</head>
<body>
<div id="a">
<div id="b">b</div>a
</div>
<script>
let a = document.getElementById("a")
a.addEventListener("click", function() {
console.log('a was clicked')
}, true)
b.addEventListener("click", function(e) {
console.log('b was clicked')
})
</script>
</body>
</html>
我们在a和b上面都注册了点击事件:
如果用addEventListener方法来注册的话,会有一个useCapture参数用于确定元素以哪个顺序接收事件,
true:在捕获阶段触发这个事件;
false: 在冒泡阶段触发这个事件;
默认为false,不在捕获阶段触发事件,而是在冒泡阶段再触发这个事件。
-
当使用true作为参数时,也就是选择在捕获阶段触发该元素绑定的事件,我们点击b元素,事件从Window对象开始,
沿着事件传播路径(事件经过的元素组成的路径)先来到a元素(因为a是b的父元素),此时事件还没有到达b,所以此时正处于冒泡阶段,
于是此时会触发a事件,控制台会打印出a was clicked,然后再来到目标对象b,此时打印b was clicked,再进入冒泡阶段。 -
相反如果我们采用默认的false参数,则是在冒泡阶段再触发事件。当事件对象经过a时,a不触发点击事件,等事件到达b,
此时打印b was clicked, 然后事件进入冒泡阶段,冒回a时,此时才触发a的点击事件,打印a was clicked
假设我们想要在点击b的时候,不触发a也绑定了的相同事件(或者反过来点击a不触发b的相同事件)怎么办呢?
addEventListener的useCapture只用于决定在哪个事件阶段触发
相应的事件,而不能决定触不触发事件。
这时候需要用到 stopPropagation 了
该事件的作用是:阻止捕获和冒泡阶段中当前事件的进一步传播。
例如我们在b的事件处理函数中加入调用stopPropagation让事件不再继续传播
a.addEventListener("click", function() {
console.log('a was clicked')
}, true)
b.addEventListener("click", function(e) {
console.log('b was clicked')
e.stopPropagation()
})
最后控制台还是打印
a was clicked
b was clicked
原因是我们给a的addEventListener函数的useCapture设置了true,它在事件捕获阶段就已经触发了它的事件,
把它设置为false,或不设置该参数即可达到相应的效果:只触发b事件,然后事件不再继续传播
关于stopPropagation的进一步说明:
假如我们有以下代码:
<button id="btn">按钮</button>
<script>
let btn = document.getElementById('btn')
btn.addEventListener('click', function() {
console.log('第一次点击')
}
)
btn.addEventListener('click', function() {
console.log('第二次点击')
}
)
</script>
我们想要在第一个添加的事件触发后,使得该事件不再继续传播,
也就是点击之后,只打印“第一次点击”,然后立马让事件停止传播,不再执行后续的事件处理函数
此时应该用stopImmediatePropagation而不是stopPropagation函数,但是这两个函数都不能阻止默认事件的发生,
要阻止默认事件的发生,使用preventDefault函数,比如选中复选框是点击复选框的默认行为,但是我们可以在
addEventListener事件处理函数中调用事件对象的preventDefault函数,阻止默认事件的发生
<body>
<p>Please click on the checkbox control.</p>
<form>
<label for="id-checkbox">Checkbox:</label>
<input type="checkbox" id="id-checkbox" />
</form>
<div id="output-box"></div>
<script>
document.querySelector("#id-checkbox").addEventListener("click", function (event) {
document.getElementById("output-box").innerHTML += "Sorry! <code>preventDefault()</code> won't let you check this!<br>";
event.preventDefault();
}, false);
</script>
</body>
相同,preventDefault也不决定了元素以哪个顺序接收事件
除此之外,还有一些概念和事件传播有着密切相关:
比如事件对象的target属性和currentTarget属性,
currentTarget: 当事件沿着 DOM 触发时事件的当前目标。它总是指向事件绑定的元素。
target: 事件触发的元素。我点击a那target就是a,点击b就是b
网友评论