美文网首页jsWeb前端之路让前端飞
JavaScript事件——事件冒泡,事件捕获,事件绑定与解绑,

JavaScript事件——事件冒泡,事件捕获,事件绑定与解绑,

作者: 卓三阳 | 来源:发表于2017-11-02 16:19 被阅读182次

    一.事件

    事件是用户或浏览器自身执行的某种动作,这是我自己的理解。


    二.事件流

    事件流描述的是从页面中接收事件的顺序,也可理解为事件在页面中传播的顺序。

    1.两种事件流模型

    冒泡型事件流:事件的传播从DOM树的叶子到根。【推荐】——event bubbling
    捕获型事件流:事件的传播葱DOM树的根到叶子。 ——event capturing

    两种事件流模型.png
    2.W3C标准模型

    W3C标准采用捕获+冒泡。两种事件流都会触发DOM的所有对象,从document对象开始,也在document对象结束。

    W3C标准模型.png
    W3C标准则取其折中方案. W3C事件模型中发生的任何事件, 先(从其祖先元素window)开始一路向下捕获, 直到达到目标元素, 其后再次从目标元素开始冒泡.
    而你作为开发者, 可以决定事件处理器是注册在捕获或者是冒泡阶段. 如果addEventListener的最后一个参数是true, 那么处理函数将在捕获阶段被触发; 否则(false), 会在冒泡阶段被触发.
    3.浏览器兼容问题

    一些标准的浏览器中,如Chrome、Firefox等,支持这两种方式。而事实上,准确的说,是这两种方式的混合方式。
    在低版本IE及Opera下,是不支持事件捕获的。而这个特点最明显体现在事件绑定函数上。
    IE、Opera的事件绑定函数是attachEvent,而Chrome等浏览器则是addEventListener,而后者比前者的参数多了一个,这个参数是一个布尔值。这个布尔值由用户决定,用户若设为true,则该绑定事件以事件捕获的形式参与,若为false则以事件冒泡的形势参与。


    三.事件绑定与解绑

    1.直接在HTML中事件处理(不推荐)

    <input type="button" value="showClick" onclick="showClick()" />

    这种方法的有很多缺点:
    (1)首先,存在一个时差问题。因为用户如果会在 HTML 元素一出现在页面上就触发相应的事件,但当时的事件处理程序有可能尚不具备执行条件。假如用户在页面解析事件处理函数之前就触发事件,就会引发错误。为此,很多HTML事件处理程序都会被封装在一个 try-catch 块中,这样错误不会浮出水面,因为在浏览器有机会处理错误之前,错误就被捕获了。如下面的例子所示:
    <input type="button" value="showClick" onclick="try{showClick();}catch(ex){}">
    (2)这样扩展事件处理程序的作用域链在不同浏览器中会导致不同结果。不同 JavaScript 引擎遵循的标识符解析规则略有差异,很可能会在访问非限定对象成员时出错。
    (3)HTML 与 JavaScript 代码紧密耦合。如果要更换事件处理程序,就要改动两个地方:HTML 代码和 JavaScript 代码。而这正是许多开发人员摒弃 HTML 事件处理程序,转而使用 JavaScript 指定事件处理程序的原因所在。

    2. 直接在dom对象上注册事件名称,就是DOM0写法,所有浏览器支持
    //事件绑定
    document.getElementById("patty").onclick = function(e){};/
    document.getElementById("patty")["onmousemover"] = function(e){};
    
    //事件解绑
    document.getElementById("patty")["onmousemover"] = null;
    
    //阻止默认事件(默认事件行为:href=""链接,submit表单提交等)
    document.getElementById("patty").onclick = function() {
        ……                         //你的代码
        return false;              //通过返回false值阻止默认事件行为
    };
    

    在这里我觉得有必要解释两点
    (1)事件绑定通过.和[]的两种方式访问js对象属性的方法,[]的形式主要是为了解决属性名不是合法的标识符,比如:object.123肯定报错,但是object["123"]就避免了这个问题,与此同时,[]的写法,更加灵活,用字符串表示属性名称,可以在运行时动态绑定事件。
    (2)return false 的含义不是阻止事件继续向顶层元素传播,而是阻止浏览器对事件的默认处理。 return false 只在当前函数有效,不会影响其他外部函数的执行。
    总结
    retrun true; 返回正确的处理结果。
    return false;返回错误的处理结果,终止处理;阻止提交表单;阻止执行默认的行为。
    return;把控制权返回给页面。

    3.DOM2事件模型

    · DOM2支持同一dom元素注册多个同种事件。
    · DOM2新增了捕获和冒泡的概念。

    (1)addEventListener(event.type, handle, boolean); IE8及以下不支持

    事件类型没有on,第三个参数false 表示在事件第三阶段(冒泡)触发,true表示在事件第一阶段(捕获)触发。 如果handle是同一个方法,只执行一次。

    var element=document.getElementById("patty");
    var handler=function(){ }
    //绑定事件
    element.addEventListener('click', handler, false);  
    //解绑事件
    element.removeEventListener('click', handle, false);
    //阻止默认事件
    element.addEventListener("click", function(e){
        var event = e || window.event;
        ……
        event.preventDefault( );      //阻止默认事件
    },false);
    
    (2)attachEvent(event.type, handle ); IE特有,兼容IE8及以下,可添加多个事件处理程序,只支持冒泡阶段,并不属于DOM2

    如果handle是同一个方法,绑定几次执行几次,这点和addEventListener不同。事件类型要加on,例如onclick而不是click

    特别注意:

    在 IE 中使用 attachEvent() 与DOM0和DOM2addEventListener有一主要区别:事件处理程序的作用域。在这些方法中,事件处理程序会在其所属元素的作用域内运行;在使用 attachEvent() 方法的情况下,事件处理程序会在全局作用域中运行,因此 this 等于 window。

    var element=document.getElementById("patty");
    var handler=function(){ }
    /绑定事件
    element.attachEvent('onclick', handler); 
    //解绑事件,参数和绑定一样
    element.detachEvent("onclick", handler);
    //阻止默认事件
    element.attachEvent("onclick", function(e){
        var event = e || window.event;
        ……
        event.returnValue = false;       //阻止默认事件
    },false);
    
    (3)封装事件绑定与解绑函数,兼容浏览器
    // 事件绑定
    function addEvent(element, eType, handler, bol) {
        if(element.addEventListener){           //如果支持addEventListener
            element.addEventListener(eType, handler bol);
        }else if(element.attachEvent){          //如果支持attachEvent
            element.attachEvent("on"+eType, handler);
        }else{                                  //否则使用兼容的onclick绑定
            element["on"+eType] = handle;
        }
    }
    
    // 事件解绑
    function removeEvent(element, eType, handler, bol) {
        if(element.addEventListener){
            element.removeEventListener(eType, handler, bol);
        }else if(element.attachEvent){
            element.detachEvent("on"+eType, handler);
        }else{
            element["on"+eType] = null;
        }
    }
    

    作为一个追求完美的人,我们可以将两个函数写成函数的方法模式

    //用事件冒泡方式,如果想兼容事件捕获只需要添加个bool参数
    var EventUtil = {
        addHandler: function(element,type,handler) {
            if (element.addEventListener) {
                element.addEventListener(type,handler,false);
            }
            else if (element.attachEvent) {
                element.attachEvent('on'+type,handler);
            }
            else {
                element['on'+type] = handler;
            }
        },
    
        removeHandler: function(element,type,handler) {
            if (element.removeEventListener)
            {
                element.removeEventListener(type,handler,false);
            }
            else if(element.detachEvent) {
                element.detachEvent('on' +type,handler);
            }
            else {
                element['on'+type] = null;
            }
        }
    }
    
    //使用
    var patty= document.getElementById("patty");
    var handler = function(){
        console.log("Don't touch patty");
    };
    EventUtil.addHandler(patty, "click", handler);
    EventUtil.removeHandler(patty, "click", handler);
    

    四.终止事件冒泡

    事件冒泡、事件捕获阻止:

    event.stopPropagation( ); // 阻止事件的进一步传播,包括(冒泡,捕获),无参数 ,ie不支持
    event.cancelBubble = true; // true 为阻止冒泡,也有浏览器不支持

    方法1:利用event.stopPropagation( )和event.cancelBubble
        var patty=document.getElementById("patty").addEventListener("click",function(event){  
        var event = event||window.event;
          ……
         event.stopPropagation();  
         });  
    

    这里提供一个阻止冒泡兼容性写法

    function stopPropagation(event){
        event=window.event||event;
        if(event.stopPropagation){ 
          event.stopPropagation();
        }else{
         event.cancelBubble=true;
        }
    }
    //直接调用
    document.getElementById("patty").addEventListener("click",function(event){  
        ......
        stopPropagation(event);
    }
    
    方法2:利用event.target和event.currentTarget,事件包含最初触发事件的节点引用 和 当前处理事件节点的引用,节点只处理自己触发的事件
    document.getElementById("patty").addEventListener("click",function(event){  
        event=window.event||event;
        //IE没有event.target,有event.srcElement    
         var target = event.target||event.srcElement;
        if(target == event.currentTarget)  
           { 
              ……
           }  
        });  
    

    方法一缺点:为了实现点击特定的元素显示对应的信息,方法一要求每个元素的子元素也必须终止事件的冒泡传递,即跟别的元素功能上强关联,这样的方法会很脆弱。
    方法二缺点:方法二为每一个元素都增加了事件监听处理函数,事件的处理逻辑都很相似,即都有判断 if(target == event.currentTarget),这样存在了很大的代码冗余,现在是三个元素还好,当有10几个,上百个又该怎么办呢?

    改进(利用事件委托)

    让某个父节点统一处理事件,通过判断事件的发生地(即事件产生的节点),然后做出相应的处理
    这里我们先了解下事件委托的概念

    事件委托:

    利用事件冒泡的特性,将里层的事件委托给外层事件,根据event对象的属性进行事件委托,改善性能。使用事件委托能够避免对特定的每个节点添加事件监听器;事件监听器是被添加到它们的父元素上。事件监听器会分析从子元素冒泡上来的事件,找到是哪个子元素的事件。

      window.onload = function() {  
          document.getElementById("box").addEventListener("click",eventPerformed,false);  
       }  
      function eventPerformed(event) {  
           var event = event||window.event;
           var target = e.target||e.srcElement;
          switch (target.id) {  
            case "div1":  
                alert("您好,我是div1。");  
                break;  
            case "div2":  
                 alert("您好,我是div2。");  
                break;  
            }  
        } 
    

    五.最后给大家送上一个跨浏览器的事件对象

    var EventUtil={
        getEvent:function(event){
            return event||window.event;
        },
        getTarget:function(event){
            return event.target||event.srcElement;
        },
        preventDefault:function(){
            if(event.preventDefault){
                event.preventDefault();
            }else{
                event.returnValue=false;
            }
        },
        stopPropagation:function(){
            if(event.stopPropagation){
                event.stopPropagation();
            }else{
                event.cancelBubble=true;
            }
        },
        addHandler:function(element,type,handler){
            if(element.addEventListener){
                element.addEventListener(type,handler,false);
            }else if(element.attachEvent){
                 element["e"+type]=function(){
                  handler.call(element)
              }
                element.attachEvent("on"+type,element["e"+type]);
            }else{
                element["on"+type]=handler;
            }
        },
        removeHandler:function(element,type,handler){
            if(element.removeEventListener){
                element.removeEventListener(type,handler,false);
            }else if(element.detachEvent){
                element.detachEvent("on"+type,element["e"+type]);
                element["e"+type]=null;    
            }else{
                element["on"+type]=null;
            }
        }
    
      };
    
    
    //使用
    var patty=document.getElementById("patty");
    var handler=function(event){
       var event=EventUtil.getEvent(event);
       var target=EventUtil.getTarget(event);
       alert(target.id);
       EventUtil.stopPropagation(event);
    }
    EventUtil.addHandler(patty,'click',handler);
    

    好了,关于JS事件暂且先写这么多,欢迎大家指正博文中错误。谢谢观看!

    相关文章

      网友评论

        本文标题:JavaScript事件——事件冒泡,事件捕获,事件绑定与解绑,

        本文链接:https://www.haomeiwen.com/subject/vjuopxtx.html