Javscript事件模型

TOC
  1. 1. 三种模型
    1. 1.1. 一.HTMl事件类型
    2. 1.2. 二.DOM0级
    3. 1.3. 三.DOM2模型
      1. 1.3.1. 标准模型
      2. 1.3.2. IE模型
  2. 2. 再说冒泡和捕获
    1. 2.1. 冒泡
    2. 2.2. 捕获
    3. 2.3. DOM2事件流

三种模型

一.HTMl事件类型

常见如 onclick="fn()" 形式,耦合度高,不利于维护

二.DOM0级

常见标准dom的写法,兼容性最好.

window.onload=function(){
    var input=document.getElementsByTagName('input')[0];
    input.onclick=test;
}
function test(){ 
    //...
};

但是:DOM0没法为一个元素原生(可能这个词用得不对)添加一次以上的事件,否则后面的事件就会把前面的覆盖掉.

btnDOM.onclick = function(){
    alert("click me!");           
}
btnDOM.onclick = function(){
         alert("click me1111!");           
} //后面的事件会覆盖前面的事件.

可是:如果硬要在DOM0下为一个元素绑定多个事件,那么还是可以实现的,只不过有点不合理而已.

例子:

var btn = document.getElementById('btn');
    btn.onclick=function(){
        alert(1);
    }
    var fn = btn.onclick;
    btn.onclick=function(){
        if(fn){
            fn();
        }
        alert(2);
    }

三.DOM2模型

标准模型

  • 事件添加:addEventListener(type,fn,bool)
  • 事件移除:removeEventListener(type,fn,bool)

DOM2事件处理里添加事件使用的是addEventListener,它接收三个参数比IE事件处理多一个,前两个的意思和ie事件处理方法的两个参数一样,唯一的区别就是第一个参数里要去掉on这个前缀,第三个参数是个布尔值,如果它的取值是true,那么事件就按照捕获方式处理,取值为false,事件就是按照冒泡处理,有第三个参数我们可以理解为什么DOM2事件处理里要把事件元素跑个两遍,目的就是为了兼容两种事件模型,不过这里要请注意下,不管我们选择是捕获还是冒泡,两遍遍历是永远进行,如果我们选择一种事件处理方式,那么另外一个事件处理流程里就不会促发任何事件处理函数,这和汽车挂空挡空转的道理一样。通过DOM2事件方法的设计,我们知道DOM2事件在运行时候只能执行两种事件处理方式中的一种,不可能两个事件流体系同时促发,所以虽然元素遍历两遍,但是事件函数绝不可能被促发两遍,注意我这里指不促发两遍是指一个事件函数,其实我们可以模拟两个事件流模型同时执行的情况,例如下面代码:

btnDOM.addEventListener("click",ftn,true);  //捕获
 btnDOM.addEventListener("click",ftn,false);    //冒泡

但这种写法是多事件处理,相当于我们点击两次按钮。

DOM2也提供了删除事件的函数,这个函数就是removeEventListener,写法如下:

btnDOM.removeEventListener(“click”,ftn,false);
使用和IE事件的一样即参数要和定义事件的参数一致,不过removeEventListener使用时候,第三个参数不传,默认是删除冒泡事件,因为第三个参数不传默认都是false,例如:

btnDOM.addEventListener(“click”,ftn,true);
btnDOM.removeEventListener(“click”,ftn); //没有传第三个参数,默认删除冒泡事件

运行之,发现事件没有被删除成功, 所以要养成事件添加和删除参数一致的习惯.

IE模型

支持这个模型的还有opera浏览器
attachEvent(type,fn)

btnDOM.attachEvent("onclick",function(){
         alert("Click Me!");

});

btnDOM.attachEvent("onclick",function(){
         alert("Click Me,too!");    //注意! 后面绑定的事件先触发!逆序触发!
}); 

detachEvent(type,fn)

btnDOM.detachEvent("onclick",function(){
         alert("Click Me,too!");
});

运行之,两个弹窗匿名函数都没有被删除,这是怎么回事?原因是在javascript的匿名函数里,两个匿名函数哪怕代码完全一样,javascript都会在内部使用不同变量存储,结果就是我们看到的现象无法删除点击事件的,因此我们的代码要这么写:

var ftn = function(){
    alert("Click Me,too!");
};

btnDOM.attachEvent("onclick",ftn);
btnDOM.detachEvent("onclick",ftn);

这样添加的方法和删除的方法就是指向了同一个对象,所以事件删除成功了。这里的场景告诉我们写事件要有个良好的习惯即操作函数要独立定义,不要用匿名函数用成了习惯。

再说冒泡和捕获

冒泡和捕获其实就是事件流处理方式,事件流又是什么呢?事件流就是从页面接受事件的顺序,好比你在一张纸上画了很多个同心圆.如果你把手指放在圆心上,那么你的手指指向的不是一个圆,而是纸上的所有圆。那么究竟是先指到最外面的大圆呢还是最里面的小圆呢,这里肯定会有不同意见,但是可以肯定的是, 所有的圆我们都指到了.
这里的意见不一致,就是事件流处理方式不同.以下是IE和Netscape提出的方式.

冒泡

冒泡是由IE提出,从深层元素上升到最上层元素.

捕获

捕获是由网景提出,从外层元素追踪到到最里层元素. 但是对IE兼容性差.

DOM2事件流

三个阶段:

  • 捕获阶段
  • 目标阶段
  • 冒泡阶段

由于兼容的问题,我们都采取冒泡阶段来绑定事件.
以下是一个常用的事件侦听组件:

var EventUtil = {
    addHandler: function(element, type, handler){
        if (element.addEventListener){
            // DOM2
            element.addEventListener(type, handler, false); //默认冒泡
        } else if (element.attachEvent){
            //IE,opera
            element.attachEvent("on" + type, handler);
        } else {
            //DOM0, 注意只对最后一次绑定的事件有效.
            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;
        }
    }
};

EventUtil.addHandler("div","click",fn);  //执行

访客评论