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
评论加载中