2017-11-24 | UNLOCK

Javascript冒泡事件机制

Javascript冒泡事件机制

1.事件

在浏览器端的应用平台,基本是都是用事件来驱动的

事件:某个动作发生,然后做出相应的动作

浏览器中,事件表示某些事情发生的信号。

2.冒泡机制

首先,什么是冒泡?

想象一下,当水底有气泡的时候,气泡是从最底部由深向浅向上上升,在这过程,气泡会经过不同深度的水。

联想:气泡就相当于事件,水就如同整个Dom树,事件从Dom树的底部层层向上传递,直到达到Dom的根节点

案例分析

我们写一个HTML页面,其中包含三个Dom元素,div外,div中,span内,其中外包含中包含内。

1
2
3
4
5
6
7
<body id="body">
<div id="box1">
<div id="box2">
<sapn id="span">我是span</sapn>
</div>
</div>
</body>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#box1 {
width: 300px;
height: 300px;
background-color: skyblue;
}
#box2 {
width: 200px;
height: 200px;
background-color: pink;
}
#span {
display: inline-block;
width: 100px;
height: 100px;
background-color: yellow;
}
#body {
background-color: rgb(0, 252, 134);
}

界面如下:

接下来我们实现如下功能:

body添加click事件监听,当body捕获到event事件时,输出事件发生的事件和触发事件的节点信息

1
2
3
4
5
6
7
8
<script>
window.onload = function() {
document.getElementById("body").addEventListener("click",eventHandler);
}
function eventHandler(event) {
console.log("时间:"+new Date(event.timeStamp)+" 产生事件的节点:" + event.target.id +"  当前节点:"+event.currentTarget.id);
}
</script>

我们依次点击“这是span”,div中,div外和body,得到以下信息

我们可以得出结论:

无论是body,body的子元素div外,还是div外的子元素div中,以及span,当这些被点击时,会产生click事件,并且最后都会传到body被捕获,接着再调用相关处理时间函数。

示意图如下:

事件在传递过程中会传递以下信息:

事件发生时间+事件发声地点+事件类型+事件当前处理者+其他

3.终止事件冒泡

现在,我们这样,当点击各自的部分时,我们让他们各自产生提示信息

1
2
3
4
5
6
7
8
9
10
11
12
13
<script>
window.onload = function() {
document.getElementById("box1").addEventListener("click",function(event){
console.log("我是最外层div。");
});
document.getElementById("box2").addEventListener("click",function(event){
console.log("我是中间div。");
});
document.getElementById("span").addEventListener("click",function(event){
console.log("我是span。");
});
}
</script>

当我们点击span时,会弹出以下信息

显示我们并不想要这样,我们只想点击哪部分就显示哪部分的提示。那么为什么会这样呢?原因就在于冒泡的机制,当我们点击span时,span会将事件冒泡给div中,然后再冒泡给div外。当冒泡到相应的元素是会触发响应函数,在从内向外的传递过程中,依次打印了各自的信息。

原理清楚了,那么该如何阻止事件的冒泡呢?

方法一

我们想象一下,一个气泡正从水底向上冒,现在我们在水中,我们不想让他向上冒了怎么办?

对,把他扎破。没有了气泡自然也就不会有冒泡了。

类似,在某个节点中,如果不想让他处理向上传递,我们可以终止事件的冒泡:

event.stopPropagation()

这个函数可以终止事件的分发,使之不会向上层传递

我们修改script的内容

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<script>
window.onload = function() {
document.getElementById("box1").addEventListener("click",function(event){
console.log("我是最外层div。");
event.stopPropagation();
});
document.getElementById("box2").addEventListener("click",function(event){
console.log("我是中间div。");
event.stopPropagation();
});
document.getElementById("span").addEventListener("click",function(event){
console.log("我是span。");
event.stopPropagation();
});
}
</script>

当我们再次点击span时,只会有span提示。

方法二

讲方法二之前,我们先分清最初触发事件的节点引用当前处理事件节点的引用

最初触发事件的节点引用:即事件产生的节点

当前处理事件节点的引用 :即处理当前事件的节点

事件产生的节点只有一个,而在传递的过程中,节点都可以处理事件。那么我们可以设置:

节点只处理自己触发的事件,不是自己产生的事件就不处理。

event.target 引用了产生此event对象的dom 节点,而event.currrentTarget 则引用了当前处理节点,我们可以通过对比这两个target 是否相等来决定是否处理事件。

例如:当span 点击事件,产生一个event 事件对象,event.target 指向了span元素,span处理此事件时,event.currentTarget 指向的也是span元素,这时判断两者相等,则执行相应的处理函数。而事件传递给 div中 的时候,event.currentTarget变成 div中,这时候判断二者不相等,即事件不是div中本身产生的,就不作响应处理逻辑。

因此我们修改script代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
   <script>
window.onload = function() {
document.getElementById("box1").addEventListener("click",function(event){
if(event.target == event.currentTarget)
{
console.log("我是最外层div。");
}
});
document.getElementById("box2").addEventListener("click",function(event){
if(event.target == event.currentTarget)
{
console.log("我是中间div。");
}
});
document.getElementById("span").addEventListener("click",function(event){
if(event.target == event.currentTarget)
{
console.log("我是span。");

}
});
}
</script>

同样可以达到我们想要的效果

那么这两个方法有什么不同呢?

比较:

方法一在于取消事件冒泡,即当某些节点取消冒泡后,事件不会再传递;

方法二在于不阻止冒泡,过滤需要处理的事件,事件处理后还会继续传递;

方法一缺点:

为了实现点击特定的元素显示对应的信息,方法一要求每个元素的子元素也必须终止事件的冒泡传递

比如,当span 元素的处理函数没有执行冒泡终止,则事件会传到div中上,这样会造成div中 的提示信息

方法二缺点:

增加了代码冗余和逻辑上的复杂度

当有几十个,几百个,那么要求每一层都要有if(event.target == event.currentTarget)

方法改进

我们看方法二,

方法二的原理是 元素收到事件后,判断事件是否符合要求,然后做相应的处理,然后事件继续冒泡往上传递;

既然事件是冒泡传递的,那可不可以让某个父节点统一处理事件,通过判断事件的发生地(即事件产生的节点),然后做出相应的处理呢?

答案是可以的,下面通过给body 元素添加事件监听,然后通过判断event.target 然后对不同的target产生不同的行为。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<script>
window.onload = function() {
document.getElementById("body").addEventListener("click",eventPerformed);
}
function eventPerformed(event) {
var target = event.target;
switch (target.id) {
case "span":
console.log("我是span。");
break;
case "box1":
console.log("我是外层div。");
break;
case "box2":
console.log("我是中间div。");
break;
}
}
</script>

结果是点击谁谁会提示!

我们把本来每个元素都要有的处理函数,都交给了其祖父节点body 元素来完成了,也就是说,span,div中,div外 将自己的响应逻辑委托给body,让它来完成相应逻辑,自己不实现相应逻辑,这个模式,就是所谓的事件委托。

如图

本篇博客参考 https://blog.csdn.net/u010349169/article/details/23927347

JS

评论加载中