js實現自定義事件(字面量 && 原型)

1.前言

我們平時在操作dom時候經常會用到onclick,onmouseover等一系列瀏覽器特定行爲的事件,
那麼自定義事件,顧名思義,就是自己定義事件類型,自己定義事件處理函數,在合適的時候需要哪個事件類型,就去調用哪個處理程序

2.js所支持的瀏覽器默認事件

瀏覽器特定行爲的事件,或者叫系統事件,js默認事件等等都行,大家知道我指的什麼就行,下文我叫他js默認事件。
js默認事件的事件綁定,事件移出等一系列操作,相信大家都有用到過,如:

//DOM0級事件處理程序
var oDiv = document.getElementById('oDiv');
oDiv.onclick = function(){
    alert("你點擊了我");
}

又或者

//DOM2級事件處理程序
var oDiv = document.getElementById('oDiv');

//非ie
oDiv.addEventListener("click",function(){
    alert("你點擊了我");
},false);  

//ie
oDiv.attachEvent("onclick", function(){
    alert("你點擊了我");
});

所有我就不做過多的研究,畢竟我們來討論js自定義事件,這裏給出一個我之前封裝過的處理js默認事件的代碼:

//跨瀏覽器的事件處理程序  
//調用時候直接用domEvent.addEvent( , , );直接調用  
//使用時候,先用addEvent添加事件,然後在handleFun裏面直接寫其他函數方法,如getEvent;  
//addEventListener和attachEvent---都是dom2級事件處理程序  
var domEvent = {  
    //element:dom對象,event:待處理的事件,handleFun:處理函數  
    //事件名稱,不含“on”,比如“click”、“mouseover”、“keydown”等  
    addEvent:function(element,event,handleFun){  
        //addEventListener----應用於mozilla  
        if(element.addEventListener){  
            element.addEventListener(event,handleFun,false);  
        }//attachEvent----應用於IE  
        else if(element.attachEvent){  
            element.attachEvent("on"+event,handleFun);  
        }//其他的選擇dom0級事件處理程序  
        else{  
            //element.οnclick===element["on"+event];  
            element["on"+event] = handleFun;  
        }  
    },  
    //事件名稱,含“on”,比如“onclick”、“onmouseover”、“onkeydown”等  
    removeEvent:function(element,event,handleFun){  
        //removeEventListener----應用於mozilla  
        if (element.removeEventListener) {  
            element.removeEventListener(event,handleFun,false);  
        }//detachEvent----應用於IE  
        else if (element.detachEvent) {  
            element.detachEvent("on"+event,handleFun);  
        }//其他的選擇dom0級事件處理程序  
        else {  
            element["on"+event] = null;  
        }  
    },  
    //阻止事件冒泡  
    stopPropagation:function(event){  
        if(event.stopPropagation){  
            event.stopPropagation();  
        }else{  
            event.cancelBubble = true;//IE阻止事件冒泡,true代表阻止  
        }  
    },  
    //阻止事件默認行爲  
    preventDefault:function(event){  
        if(event.preventDefault){  
            event.preventDefault();  
        }else{  
            event.returnValue = false;//IE阻止事件冒泡,false代表阻止  
        }  
    },  
    //獲得事件元素  
    //event.target--非IE  
    //event.srcElement--IE  
    getElement:function(event){  
        return event.target || event.srcElement;  
    },  
    //獲得事件  
    getEvent:function(event){  
        return event? event : window.event;  
    },  
    //獲得事件類型  
    getType:function(event){  
        return event.type;  
    }  
};  

接下類我們進入正題,js自定義事件

3.對象直接量封裝js自定義事件

根據上面的封裝,我們可以這樣構思

var eventTarget = {
    addEvent: function(){
        //添加事件
    },
    fireEvent: function(){
        //觸發事件
    },
    removeEvent: function(){
        //移除事件
    }
};

相信這樣大家還是比較好理解的,然後又有一個問題大家可以想到,那就是,js默認事件,js可以一一對應,知道那個是那個,那麼我們的自定義事件呢,這個一一對應的映射表只能我們自己去建立,然後我這樣

var eventTarget = {
    //保存映射
    handlers:{},
    addEvent: function(){
        //處理代碼
    },
    fireEvent: function(){
        //觸發代碼
    },
    removeEvent: function(){
        //移出代碼
    }
};

我是這樣構建這個映射關係的

handlers = {
    "type1":[
        "fun1",
        "fun2",
       // "..."
    ],
    "type2":[
        "fun1",
        "fun2"
       // "..."
    ]
    //"..."
}

這樣每一個類型可以有多個處理函數,以便於我們以後擴充
接下來就是代碼方面的實戰的,編寫具體的處理代碼了…

相信大家對於這個思路已經很清楚了,我直接附上代碼

//直接量處理js自定義事件
var eventTarget = {
    //保存事件類型,處理函數數組映射
    handlers:{},
    //註冊給定類型的事件處理程序,
    //type -> 自定義事件類型, handler -> 自定義事件回調函數
    addEvent: function(type, handler){
        //判斷事件處理數組是否有該類型事件
        if(eventTarget.handlers[type] == undefined){
            eventTarget.handlers[type] = [];
        }
        //將處理事件push到事件處理數組裏面
        eventTarget.handlers[type].push(handler);
    },
    //觸發一個事件
    //event -> 爲一個js對象,屬性中至少包含type屬性,
    //因爲類型是必須的,其次可以傳一些處理函數需要的其他變量參數。(這也是爲什麼要傳js對象的原因)
    fireEvent: function(event){
        //判斷是否存在該事件類型
        if(eventTarget.handlers[event.type] instanceof Array){
            var _handler = eventTarget.handlers[event.type];
            //在同一個事件類型下的可能存在多種處理事件,找出本次需要處理的事件
            for(var i = 0; i < _handler.length; i++){
                //執行觸發
                _handler[i](event);
            }
        }
    },
    //註銷事件
    //type -> 自定義事件類型, handler -> 自定義事件回調函數
    removeEvent: function(type, handler){
        if(eventTarget.handlers[type] instanceof Array){
            var _handler = eventTarget.handlers[type];
            //在同一個事件類型下的可能存在多種處理事件,找出本次需要處理的事件
            for(var i = 0; i < _handler.length; i++){
                //找出本次需要處理的事件下標
                if(_handler[i] == handler){
                    break;
                }
            }
            //刪除處理事件
            _handler.splice(i, 1);
        }
    }
};

這是一種調用運行的方法

eventTarget.addEvent("eat",function(){
    console.log(123);   //123
});
eventTarget.fireEvent({type: "eat"});

這種方法有一個缺點,不能刪除該處理事件,因爲我們是用映射表做的,而且也不提倡,直接給映射表裏面存這麼多數據,有點多。

另一種方法,將處理事件提取出來(推薦)

function b(){
     console.log(123);
}
eventTarget.addEvent("eat",b);
eventTarget.fireEvent({
    type: "eat"
});                                     //123
eventTarget.removeEvent("eat",b);
eventTarget.fireEvent({type: "eat"});   //空

也可以這樣,傳遞更多的參數

eventTarget.fireEvent({
    type: "eat",
    food: "banana"
}); 
function b(data){
     console.log(data.food);  //banana
}

總結:字面量這種方法,有點兒缺點,就是萬一一不小心,把某個屬性在handler函數裏面,賦值null,這樣會造成我們的的eventTarget 方法崩盤。看來原型應該是個好方法,更安全一點。

4.對象原型封裝js自定義事件

由於前面思路基本都講清楚了,這裏我直接附上代碼,大家可以研究下其中的利弊,或許你可以找到更好的方法解決Ta…

//自定義事件構造函數
function EventTarget(){
    //事件處理程序數組集合
    this.handlers = {};
}
//自定義事件的原型對象
EventTarget.prototype = {
    //設置原型構造函數鏈
    constructor: EventTarget,
    //註冊給定類型的事件處理程序,
    //type -> 自定義事件類型, handler -> 自定義事件回調函數
    addEvent: function(type, handler){
        //判斷事件處理數組是否有該類型事件
        if(typeof this.handlers[type] == 'undefined'){
            this.handlers[type] = [];
        }
        //將處理事件push到事件處理數組裏面
        this.handlers[type].push(handler);
    },
    //觸發一個事件
    //event -> 爲一個js對象,屬性中至少包含type屬性,
    //因爲類型是必須的,其次可以傳一些處理函數需要的其他變量參數。(這也是爲什麼要傳js對象的原因)
    fireEvent: function(event){
        //模擬真實事件的event
        if(!event.target){
            event.target = this;
        }
        //判斷是否存在該事件類型
        if(this.handlers[event.type] instanceof Array){
            var handlers = this.handlers[event.type];
            //在同一個事件類型下的可能存在多種處理事件,找出本次需要處理的事件
            for(var i = 0; i < handlers.length; i++){
                //執行觸發
                handlers[i](event);
            }
        }
    },
    //註銷事件
    //type -> 自定義事件類型, handler -> 自定義事件回調函數
    removeEvent: function(type, handler){
        //判斷是否存在該事件類型
        if(this.handlers[type] instanceof Array){
            var handlers = this.handlers[type];
            //在同一個事件類型下的可能存在多種處理事件
            for(var i = 0; i < handlers.length; i++){
                //找出本次需要處理的事件下標
                if(handlers[i] == handler){
                    break;
                }
            }
            //從事件處理數組裏面刪除
            handlers.splice(i, 1);
        }
    }
};

調用方法

function b(){
    console.log(123);
}

var target = new EventTarget();
target.addEvent("eat", b);

target.fireEvent({
    type: "eat"
});                                 //123

原型這種方法,與直接量方法功能是一樣的…

呼…也就基本說完了,如果哪裏說得不對,歡迎討論,後續有什麼更好的理解也不斷完善…

發佈了57 篇原創文章 · 獲贊 84 · 訪問量 17萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章