利用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中的實現方式;

相關文章
相關標籤/搜索