JavaScript 自定義事件(一)

所謂自定義事件,就是有別于帶有瀏覽器特定行為的事件(鼠標事件,鍵盤事件,html事件等),事件名稱可以自定義,可以通過特定的方法進行添加,觸發以及刪除。自定義事件相當于是 觀察者模式 ,可以把復雜邏輯解耦,代碼可以寫的很清晰,而且很容易復用。

一.JS事件應用初印象

我覺得更為準確的說,下面列舉的是Dom事件的應用。我們在這里可以通過了解JS關于Dom事件的綁定,觸發,解綁來一步一步書寫我們自己的JS自定義事件。關于自定義事件,我決定分兩步講解。第一步,也就是這篇文章將要提到的循序漸進的一步一步完善自定義JS事件。第二步,我們封裝一個與Dom元素有關的自定義事件函數。

// 事件綁定
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;
    }
}
//實例應用
var patty=document.getElementById("patty");
var sayHello=function (){
   alert("Hello!!!");
}
addEvent(patty,click,sayHello,false); //添加點擊事件

這里我們關注下click事件的實現。我們可以很清楚的將其分成三部分:添加,觸發,刪除。
同時,我們必須考慮一個問題:事件和其處理函數應該怎樣存儲。事件分為很多種,click, mouseover, submit, keydown等等,每一種事件下又可以添加處理函數。這種一對多的映射關系,我們可以很自然想到用下面這樣數據結構來存儲事件。

_listener = {
"click": [func1, func2],
"custom": [func3],
...
}

二.自定義我們自己的JS事件

1.函數式實現
var _listener = {};   //存儲事件和其處理函數
var addEvent = function(type, fn) {
      //添加
};
var fireEvent = function(type) {
     //觸發
};
var removeEvent = function(type, fn) {
     //刪除
};

//添加eat事件
addEvent("eat",function(){
  alert("eat an apple!");
})
// 觸發自定義eat事件
fireEvent(eat);

我們沒有詳細展示函數式實現的代碼,是因為這種寫法較為基礎且過多地暴露全局變量,我們稍微了解即可,具體函數地實現,我們在下面的方式會實現。

2.用字面量方式實現
var Event = {
    _listeners: {},    
    // 添加
    addEvent: function(type, fn) {
        if (typeof this._listeners[type] === "undefined") {
            this._listeners[type] = [];
        }
        if (typeof fn === "function") {
            this._listeners[type].push(fn);
        }    
        return this;
    },
    // 觸發
    fireEvent: function(type) {
        var arrayEvent = this._listeners[type];
        if (arrayEvent instanceof Array) {
            for (var i=0, length=arrayEvent.length; i<length; i+=1) {
                if (typeof arrayEvent[i] === "function") {
                    arrayEvent[i]({ type: type });    
                }
            }
        }    
        return this;
    },
    // 刪除
    removeEvent: function(type, fn) {
        var arrayEvent = this._listeners[type];
        if (typeof type === "string" && arrayEvent instanceof Array) {
            if (typeof fn === "function") {
                // 清除當前type類型事件下對應fn方法
                for (var i=0, length=arrayEvent.length; i<length; i+=1){
                    if (arrayEvent[i] === fn){
                        this._listeners[type].splice(i, 1);
                        break;
                    }
                }
            } else {
                // 如果僅僅參數type, 或參數fn邪魔外道,則所有type類型事件清除
                delete this._listeners[type];
            }
        }
        return this;
    }
};

//添加eat事件
Event.addEvent("eat",function(){
  alert("eat an apple!");
})
// 觸發自定義eat事件
Event.fireEvent(eat);

字面量實現雖然減少了全局變量,但是其屬性方法等都是暴露而且都是唯一的,一旦某個關鍵屬性(如_listeners)不小心在某事件處reset了,則整個全局的自定義事件都會崩潰。因此,我們可以進一步改進,例如,使用原型鏈繼承,讓繼承的屬性(如_listeners)即使出問題也不會影響全局。

3.原型模式實現
var EventTarget = function() {
    this._listener = {};
};

EventTarget.prototype = {
    constructor:EventTarget,
    addEvent: function(type, fn) {
        if (typeof type === "string" && typeof fn === "function") {
            if (typeof this._listener[type] === "undefined") {
                this._listener[type] = [fn];
            } else {
                this._listener[type].push(fn);    
            }
        }
        return this;
    },
    addEvents: function(obj) {
        obj = typeof obj === "object"? obj : {};
        var type;
        for (type in obj) {
            if ( type && typeof obj[type] === "function") {
                this.addEvent(type, obj[type]);    
            }
        }
        return this;
    },
    fireEvent: function(type) {
        if (type && this._listener[type]) {
            var events = {
                type: type,
                target: this    
            };
            
            for (var length = this._listener[type].length, start=0; start<length; start+=1) {
                this._listener[type][start].call(this, events);
            }
        }
        return this;
    },
    fireEvents: function(array) {
        if (array instanceof Array) {
            for (var i=0, length = array.length; i<length; i+=1) {
                this.fireEvent(array[i]);
            }
        }
        return this;
    },
    removeEvent: function(type, key) {
        var listeners = this._listener[type];
        if (listeners instanceof Array) {
            if (typeof key === "function") {
                for (var i=0, length=listeners.length; i<length; i+=1){
                    if (listeners[i] === key){
                        listeners.splice(i, 1);
                        break;
                    }
                }
            } else if (key instanceof Array) {
                for (var lis=0, lenkey = key.length; lis<lenkey; lis+=1) {
                    this.removeEvent(type, key[lenkey]);
                }
            } else {
                delete this._listener[type];
            }
        }
        return this;
    },
    removeEvents: function(params) {
        if (params instanceof Array) {
            for (var i=0, length = params.length; i<length; i+=1) {
                this.removeEvent(params[i]);
            }    
        } else if (typeof params === "object") {
            for (var type in params) {
                this.removeEvent(type, params[type]);    
            }
        }
        return this;    
    }
};

//使用
var event1=new EventTarget();
var event2=new EventTarget();

這樣我們發現,event1和event2在共享方法的同時,又有自己的_listener 屬性,彼此之間不會污染。

我相信大家通過上面的了解,對JS自定義事件一定有更深的理解了,OK這次就先講這么多,謝謝觀看!

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容