事件冒泡--了解事件委托全流程

2019-03-10 11:51:41来源:博客园 阅读 ()

新老客户大回馈,云服务器低至5折

说是初认识,其实也不算了,刚学习JS时就已经听过事件的冒泡和捕获的大名,但真是不知所云,也是不求甚解,迷惑了很久,今天终决定好好来了解一下这个冒泡。

在此之前呢,不得不提一下事件流:描述的是从页面中接受事件的顺序。

什么是冒泡

当事件发生后,这个事件就要开始传播(从里到外或者从外向里)。为什么要传播呢?因为事件源本身(可能)并没有处理事件的能力,即处理事件的函数(方法)并未绑定在该事件源上。例如我们点击一个按钮时,就会产生一个click事件,但这个按钮本身可能不能处理这个事件,事件必须从这个按钮传播出去,从而到达能够处理这个事件的代码中(例如我们给按钮的onclick属性赋一个函数的名字,就是让这个函数去处理该按钮的click事件),或者按钮的父级绑定有事件函数,当该点击事件发生在按钮上,按钮本身并无处理事件函数,则传播到父级去处理。

按我自己的理解,冒泡就真的是鱼儿吐泡泡一样,从水底冒一个泡咕噜咕噜漂到了水面,这个顺序是自下往上的;而捕获呢,就像捕鱼一样从水面上撒网沉到水底,这个顺序是自上往下的。这样类比到DOM树中应该就能够记得很形象了。也就是说,事件冒泡 :当一个元素接收到事件的时候,会把它接收到的事件传给自己的父级,一直到window。

但有一点需要注意的,这里传递的仅仅是事件,并不传递所绑定的事件函数。所以如果父级没有绑定事件函数,就算传递了事件,也不会有什么表现,但事件确实是传递了。我们用代码来具体的表示,

 HTML结构:

1 <div id="div1" style="width: 50px;height: 50px;background: red;">
2     <div id="div2" style="width: 30px;height: 30px;background: blue;"></div>
3 </div>

JS部分:

1 var div1 = document.getElementById("div1");
2 var div2 = document.getElementById("div2");
3 div2.onclick = function(){
4     alert("div2");
5 }
6 div1.onclick = function(){
7     alert("div1");
8 }

这段小代码很简单,定义了一个父子关系的div元素,分别增加了点击事件。当我们点击子元素div2时,先会弹出它自身的事件“div2”,然后又弹出了“div1”。这就说明了点击子元素的时候不仅仅触发了子元素自己的事件,它的父元素事件也被触发 了。这样的现象就叫做冒泡。

再举个例子,还是刚刚的代码,我们来做一些小修改,删除子元素的点击事件!

?

可以看一下最终的效果图,当点击子元素div2时,会弹出事件“div1”,这再次证明了触发了冒泡模式,当点击了没有绑定事件的子元素,父元素的点击事件被触发,执行了自己绑定的函数。此外,这种事件的触发,和CSS样式是没有关系的。可以改变父子元素的position位置,分离两个元素的位置,这时仍会触发冒泡。

当然,如果只删除了父元素div1的点击事件,那点击子元素时只会触发子元素的事件,而传递给父元素的只是事件,但因为父元素自身没有绑定任何事件,就算传递给它事件,父元素仍然无法触发事件。

冒泡的优点

至此,我们算是初认识了事件冒泡,会不会感觉这没什么用呀,其实冒泡真有一大优点,就是产生了事件委托。所谓事件委托:

事件委托: 由于事件的冒泡,我们点击子元素的时候,会把事件一层层的传递给父级元素。相反的,我们操作元素的时候,直接把事件绑定在父级元素上,而不是分别给子元素绑定事件。通过判断子元素,从而达到同样的效果

先上例子,假设情形:有5个列表,我们点击其中一个就会改变其背景颜色为红色。

?

 HTML结构很简单:

1 <ul id="ul">
2     <li>这就是个测试</li>
3     <li>这就是个测试</li>
4     <li>这就是个测试</li>
5     <li>这就是个测试</li>
6     <li>这就是个测试</li>
7 </ul>

JS部分:

1 var ul = document.getElementById("ul");
2 var li = ul.getElementsByTagName("li");
3 for(var i=0; i<li.length; i++){
4     li[i].onclick = function(){
5         this.style.background = "red";
6     }
7 }

通过循环给每一个 li 加事件,感觉应该也没什么问题,但其实这是非常耗费性能的。就算不考虑性能吧,那就假设一个很极端的情况,如果我想在第6个位置添加一个div元素,第7个位置添加一个p元素,同样的内容要实现同样的效果,那是不是就需要另外给这个div和p分别添加一个点击事假,这就很麻烦了。

如果我们可以给父元素ul添加事件,作用于全部子元素,那不是一劳永逸嘛,而这个就是通过冒泡模式进行的事件委托实现的。但是这就会有个问题,我选中的是ul,那么点击其中的任一 li 时,岂不是全部的 ul 列表都变红了嘛,这与初始需求相违背了。这就需要新概念的登场了,能够准确获取你当前鼠标所指的 li 的事件源。

事件源:不管事件绑定在那个元素中,都指的是实际触发事件的那个的目标

这也存在IE的兼容性问题:

  • IE:window.event.srcElement
  • 标准的W3C 方式:event.target

 那么做一下兼容性处理,JS部分变为:

1 var ul = document.getElementById("ul");
2 var li = ul.getElementsByTagName("li");
3 
4 ul.onclick = function(ev){
5     var event = ev || window.event;
6     var target = event.target ||event.srcElement;
7     target.style.background = "red";
8 }

这样就很好的提高了性能,而且在增加其他子元素时候,同样可以满足需求。

冒泡的缺点

但是哟,冒泡模式也存在一些困扰,譬如假设一个情形:

?

需求是,点击div1时候,能够让div2显示,再点击其他地方时会让div2隐藏。那JS可以这样写: 

1 var div1 = document.getElementById("div1");
2 var div2 = document.getElementById("div2");
3 
4 div1.onclick = function(){
5     div2.style.display = "block";
6 }
7 document.onclick = function(){
8     div2.style.display = "none";
9 }

去浏览器验证最终效果,很神奇的发现点击了红色部分,蓝色方块消失了!明明是需要让它显示的嘛,看一下我们写的代码,逻辑很简单直白,完全无懈可击呀。

这就是冒泡惹的祸,由于事件冒泡,点击div1时候,事件向上传递,一直传到了document,而此时的document也绑定了自己的事件,那就正好触发了,也就是让蓝色方块隐藏了。所以,也不能说点击红色方块没有让蓝色方块显示,而是这个过程太短暂 了,还没显示呢就已经触发了最终的document的事件。可以在div1事件中添加一个弹框用来测试是否进行了这一步骤。

因此,这种情况下我们就不希望存在冒泡了,那就需要用到取消事件的冒泡的两种方式:

  • 标准的W3C 方式:e.stopPropagation();这里的stopPropagation是标准的事件对象的一个方法,调用即可
  • 非标准的IE方式:ev.cancelBubble=true;  这里的cancelBubble是 IE事件对象的属性,设为true就可以了

因为这存在IE和其他浏览器的差异,所以会做一个判断,封装成一个函数:

1 function stopBubble(e) {
2    if ( e && e.stopPropagation )
3       e.stopPropagation();
4   else
5     window.event.cancelBubble = true;
6 }

首先,判断这个浏览器是否支持stopPropagation(),如果支持说明是非IE,可以使用标准的W3C方式,不然就是用IE提供的办法。有了这样一个阻止冒泡的函数,那就在div1事件中调用这个函数,让我们点击div1时不会再往上冒泡传递事件,也就不会触发到document的事件了,这样当初的需求完美实现了。


原文链接:https://www.cnblogs.com/zuoWendong/p/10489029.html
如有疑问请与原作者联系

标签:

版权申明:本站文章部分自网络,如有侵权,请联系:west999com@outlook.com
特别注意:本站所有转载文章言论不代表本站观点,本站所提供的摄影照片,插画,设计作品,如需使用,请与原作者联系,版权归原作者所有

上一篇:前端angular使用crypto-js进行加密

下一篇:【Leetcode】【简单】【682棒球比赛】【JavaScript】