我们经常会碰到这么一个现象,我们在逛社区的时候,看到优秀的文章,我们就会给他点赞,就像这样:
我们可以不需要点进去文章就可以直接点赞,按照正常的逻辑,我们应该点进去才能点赞的,因为点赞应该是内层的元素,我们怎么能隔山打牛穿过它的父级元素执行点赞这个事件呢。
这就与我们接下来要学习的事件流 息息相关了,在 JavaScript 中,事件触发过程涉及两个主要机制:事件捕获 (Event Capturing)和事件冒泡(Event Bubbling),这两个机制的默认顺序是事件冒泡,但我们也可以通过调用事件对象的方法来切换到事件捕获模式。
示例代码:
html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<style>
*{
margin: 0;
padding: 0;
}
#a{
width: 400px;
height: 400px;
background-color: #b92929;
}
#b{
width: 200px;
height: 200px;
background-color: #1caa55;
}
#c{
width: 100px;
height: 100px;
background-color: #000000;
}
</style>
<body>
<div id="a">
<div id="b">
<div id="c">
</div>
</div>
</div>
</body>
<script>
let a = document.getElementById("a");
let b = document.getElementById("b");
let c = document.getElementById("c");
a.addEventListener("click",function(){
console.log("点击a");
},true)
b.addEventListener("click",function(){
console.log("点击b");
},true)
c.addEventListener("click",function(){
console.log("点击c");
},true)
</script>
</html>
事件捕获机制
概念:在事件触发的过程中,浏览器首先从文档根元素开始向里面寻找目标元素,这个过程称为事件捕获。通过事件捕获,可以在事件到达目标元素之前捕获到事件,并执行相应的处理函数。
在上面代码中,我们将监听事件的第三个参数设置为true,这样我们就可以切换到事件捕获模式,当我们点击c时,浏览器从根元素开始向里面开始寻找目标元素,然后在这个过程中依次捕获到了点击事件a,点击事件b,最后才找到目标元素c,捕获到点击事件c,所以会依次打印 点击a 点击b 点击c
。
事件冒泡机制
概念:一旦事件到达目标元素并执行相应的处理函数后,它会开始在目标元素上方的父元素中冒泡,直到到达文档根元素。这个过程称为事件冒泡。通过事件冒泡,可以在事件冒泡阶段捕获到事件,并执行相应的处理函数。
我们不做任何设置时(没有设置监听事件的第三个参数),默认就是冒泡机制,也很好理解,其实就是和捕获机制相反的一个过程,就好比你往墙上丢了一个球,这个球会反弹回来,这个球往墙上飞的过程就是事件的捕获过程,而球弹回来的过程就是冒泡的过程。我们点击c,浏览器找到目标元素时,就会向外层的父级元素冒泡,所以在这个示例中,我们依次会执行点击事件c、点击事件b、点击事件a,打印结果:点击c 点击b 点击a
。
stopPropagation 和 stopImmediatePropagation
那么如果我们不希望事件继续传播,还是这个例子,假设在默认情况下,我们点击了c,然后只想打印c而不希望触发点击事件b和点击事件a,那我们该怎么做呢
在Javascript有两个常用的方法:
stopPropagation()
和 stopImmediatePropagation()
都是用于阻止事件进一步传播的方法,但它们在功能和作用上有一些不同。
- stopPropagation() :
stopPropagation()
方法用于阻止事件的冒泡或捕获过程。当我们调用stopPropagation()
方法时,事件将停止在当前元素上触发其他相同类型的事件处理函数,并停止继续向上或向下传播到其他元素。这意味着其他父级或子级元素上的此类的事件处理函数将不会被触发。但如果当前元素上绑定了多个相同类型的事件处理函数,除了当前正在执行的处理函数外,其他处理函数仍然会被执行。 如下: 只会打印 点击c和点击d
js
a.addEventListener("click",function(){
console.log("点击a"); // 阻止事件冒泡,其他父级元素上的点击事件处理函数不会被触发
})
b.addEventListener("click",function(){
console.log("点击b");
})
c.addEventListener("click",function(e){
e.stopPropagation()
console.log("点击c");
})
c.addEventListener("click",function(e){
console.log("点击d");
})
- stopImmediatePropagation() :
stopImmediatePropagation()
方法也用于阻止事件的传播,但它的作用更为强大。当您调用stopImmediatePropagation()
方法时,事件将立即停止,并且不会再触发当前元素上剩余的任何相同类型的事件处理函数,也不会继续传播到其他元素。这包括当前元素上绑定的其他事件处理函数,以及同级别元素上的事件处理函数。换句话说,除了当前正在执行的处理函数外,其他任何处理函数都不会被执行。
如下只会打印点击c
javascript
a.addEventListener("click",function(){
console.log("点击a");
})
b.addEventListener("click",function(){
console.log("点击b");
})
c.addEventListener("click",function(e){
e.stopImmediatePropagation() // 阻止事件传播并停止执行后续的处理函数
console.log("点击c");
})
c.addEventListener("click",function(e){
console.log("点击d");
})
这就可以解释为什么这里可以"隔山打牛"了