jsWeb前端之路让前端飞

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

2017-11-02  本文已影响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事件暂且先写这么多,欢迎大家指正博文中错误。谢谢观看!

上一篇下一篇

猜你喜欢

热点阅读