zoukankan      html  css  js  c++  java
  • 事件冒泡了解事件委托全流程

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

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

    什么是冒泡

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

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

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

     HTML结构:

    1 <div id="div1" style=" 50px;height: 50px;background: red;">
    2     <div id="div2" style=" 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的事件了,这样当初的需求完美实现了。

  • 相关阅读:
    32位和64位系统区别及int字节数
    c语言指针占几个字节
    可重入和不可重入
    C中的volatile用法
    让你分分钟读懂CPU架构及芯片厂商
    手机CPU知识扫盲:谈谈手机CPU架构与原理 (全
    IO端口、IO内存、IO空间、内存空间的含义和联系
    IO端口和IO内存的区别及分别使用的函数接口
    linux终端下 编译c语言程序
    git各种撤销操作
  • 原文地址:https://www.cnblogs.com/zuoWendong/p/10489029.html
Copyright © 2011-2022 走看看