利用DOM0级事件模拟简单的事件系统

让咱们回到尚未DOM2级事件方法的年代,那时候,咱们想给某元素绑定一个事件方法时,是这样作的:javascript

javascriptvar ele=document.getElementById("main");
ele.onclick=function(){
    console.log("作一件事");
}

这就是DOM0级事件方法,咱们须要用XXX.onYYY=ZZZ这种形式来绑定事件,当点击元素时,会输出“作一件事”,恩,很理想。java

接下来,当咱们但愿在点击该元素的时候再作另外一件事怎么办呢?下面这样:web

javascriptele.onclick=function(){
   console.log("作第二件事");
}

这样确定不行,由于咱们是但愿点击时既输出“作一件事”,又要输出“作第二件事”。而这样的话后面的方法将前面的覆盖掉了,由于XXX.onYYY=ZZZ只能给元素的某个事件类型(如例子中的click事件)绑定一个方法ZZZ;这个问题难不到咱们,既然这样,那咱们就把要作的事情全都放在ZZZ里不就好了么;数组

javascriptfunction fn1(){
    console.log("作一件事");
}
function fn2(){
   console.log("作第二件事");
}
ele.onclick=function(){
    fn1();
    fn2();
}

接下来,挑战来了,那若是咱们是但愿在点击元素时作100件不一样的事情呢?难道也是这样用XXX.onYYY=ZZZ形式,将100个方法都在ZZZ里依次执行吗?显然这是很笨的方法;动一下脑筋,很快想到另外一个解决方案:
明确如下需求,咱们是但愿在点击事件发生时,执行多个方法,而具体有多少个,一开始时不明确的,那么咱们能够用一个数组来保存全部的回调方法;当点击发生时,执行一个方法,在这个方法中依次执行数组中的回调方法。函数

javascriptvar arr=[];
ele.onclick=function(){
    for(var i=0;i<arr.length;i++){
        if(typeof arr[i] === "function"){
            arr[i]();
        }
    }
}

arr.push(fn1);
arr.push(fn2);

这样的话,当咱们须要给元素点击事件添加更多的方法时,直接往数组arr中push更多的回调方法就好了,甚至当咱们但愿去掉某个方法,也能够经过删除数组中的某项来完成。
可是这样并不能实际使用,由于用来保存事件的数组当前是做为一个全局变量。为了持久化保存这个数组,咱们能够这样,将这个数组定义为元素节点对象的某一个属性,就像下面这样:this

javascriptele.eventList=[];
ele.onclick=function(){
    for(var i=0;i<btn.eventList.length;i++){
        btn.eventList[i]();
    }
}

ele.eventList.push(fn1);
ele.eventList.push(fn2);

以上代码只是针对click事件,在实际运用时,有各类不一样的事件类型;在某个类型事件触发时,须要执行全部绑定到该类型事件的方法,所以,针对每一个事件类型,都要采用一个数组来保存相应回调方法。
另外,为了在某类型事件触发时,找到针对与该事件类型的全部方法并执行,咱们能够将这个数组直接以事件类型名称命名;
而后考虑将以上方法用一个函数封装起来:以下code

javascriptfunction bind(ele,ev,callback){
    if(!ele.ev){
        ele.ev=[];//直接将保存某个事件类型全部回调方法的数组命名为事件类型
        ele["on"+ev]=function(){
            for(var i=0;i<ele.ev.length;i++){
                ele.ev[i].call(ele);//这里别忘了用call改变this关键字
            }
        }
    }
    ele.ev.push(callback);
}

为了防止重复绑定,在将回调函数push进事件队列数组时,首先须要判断被绑定的回调是否已经存在于事件队列中;对象

javascriptfunction bind(ele,ev,callback){
    if(!ele.ev){
        ele.ev=[];
        ele["on"+ev]=function(){ //只须要绑定一次
            for(var i=0;i<ele.ev.length;i++){
                ele.ev[i].call(ele);//这里别忘了用call改变this关键字
            }
        }
    }

    //因为IE9如下不支持数组的indexOf方法,因此须要遍历查找
    var flag=true;
    for(var i=0;i<ele.ev.length;i++){
        if(ele.ev[i]==callback){
            flag=false;
            break;
        }
    }
    if(flag){
        ele.ev.push(callback);
    }
}

而相应unbind方法,只须要将须要解绑的回调函数从相应的函数队列中删除便可;
另外还有如下一个问题:上述这种状况下,绑定了几个类型的事件,就会给ele多添加几个属性,命名空间污染极其严重;因此采用的方案是在元素上只添加一个属性(如_event),而后全部的事件队列全都扩展在这个属性上。blog


所有代码以下:队列

完整版代码

javascriptfunction bind(ele,ev,callback){
    if(!ele._event){
        ele._event={};
    }
    if(!ele._event[ev]){
        ele._event[ev]=[];
        ele["on"+ev]=function(e){
            e=e||window.event;

            var eList=ele._event[ev];
            for(var i=0;i<eList.length;i++){
                eList[i].call(ele,e);
            }
        }
    }

    var flag=true;
    for(var i=0;i<ele._event[ev].length;i++){
        if(ele._event[ev][i] == callback){
            flag=false;
            break;
        }
    }
    if(flag){
        ele._event[ev].push(callback);
    }
}


function unbind(ele,ev,callback){
    try{
        var eList=ele._event[ev];
        for(var i=0;i<eList.length;i++){
            if(eList[i]==callback){
                eList.splice(i,1);
                return 1;
            }
        }
    }catch(e){
        //这里主要防止在未调用bind初始化就调用unbind时出错。
        console.log("请先使用bind绑定事件。");
    }
}

在使用时传入三个参数,元素,事件类型,回调方法,便可。解绑时传入一样的三个参数;这个简单的方案解决了如下问题:

  1. this关键字指向问题
  2. 重复绑定问题
  3. 执行顺序问题

以上,咱们就完成了一个粗糙的事件系统,它的思想很相似于Dean Edward在其发布的addEvent.js中的实现方式;

相关文章
相关标签/搜索