自定義瀏覽器事件,模擬瀏覽器事件流
阿新 • • 發佈:2018-10-31
簡述
我認為,事件是我們前端最為熟悉的程式設計模式,在前端開發中能接觸太多太多,而且相對而言,事件是一種相對容易理解,邏輯性高的的模式,對於優化元件/外掛的邏輯是一個很好的應用模式。
這文章主要是用JavaScript實現3級dom事件機制,後面的更新會涉及應用倒元件開發當中。
- 瀏覽器事件一:事件的基礎應用
- 瀏覽器事件二:瀏覽器是如何實現事件
- 瀏覽器事件三:自定義事件
如何實現
jQuery 實現方式
jQuery 是實現了一個獨立的事件方法,甚至可以在自定義物件(object)上繫結事件,功能十分強大。
例:
//import jQuery $(selector).on('yourEvent',function(){ console.log('i m yourEvent handler'); }) $(selector).trigger('yourEvent')
上面例子就是jQuery 的自定義事件實現方式,下面再展示一個繫結物件上的事件,它其實是一個訂閱/釋出模式的實現 —> cowboy 實現的簡易訂閱/釋出
例:
(function($) { var o = $({}); $.subscribe = function() { o.on.apply(o, arguments); }; $.unsubscribe = function() { o.off.apply(o, arguments); }; $.publish = function() { o.trigger.apply(o, arguments); }; }(jQuery));
jQuery的事件實現原理這裡不作陳述,這裡提供Aaron的jQuery原始碼解析傳送門
jQuery實現的優點
- 良好的維護性
jQuery是一個使用率最高的前端庫,這意味這它能提供非常友好的文件,以及學習社群。從專案的角度上來看這是提高維護性的一個做法。 - 良好的相容性
解決相容性是意見相當麻煩瑣碎的事情,如果沒有充足的經驗,很難再編碼的的開始就預計到那些相容問題,所以jQuery 本身某種層度就是提供相容處理封裝。
jQuery實現的缺點
- 依賴jQuery
實現上,我們並不是所有專案都需要依賴jQuery,jQuery本身雖然十分好,但是jq鏈式操作dom寫出來的頁面/元件,再可讀性上確實不盡人意,業界都是認同那是一坨義大利麵(特別是webapp專案 - 能實現的功能有限
如果單純做元件抽象,或者基於監聽響應實現的物件,jq的實現確實是滿足並且能提供簡潔的實現方法,但是在某些需求上不一定能滿足,如:如果我們希望元件的實現邏輯更加清晰,希望加入一個事件響應流,能在元件之間鏈式傳播。 - 效能上
jQuery事件實現內部也是已經實現事件的,如果在jq之上在新增自定義的事件功能,效能上必然有影響(不可控),效能影響不會很大,但做效能優化時會是一個問題。
任意物件繫結事件
在原生JavaScript物件,自定義的object 物件並沒有事件的概念, 而實際上 我們希望某些元件擁有事件驅動,js擁有完整的事件驅動物件,但是我們開發者沒有辦法使用(第二章有提及),不過我們還是能基於官方的文件事件一個與js一樣的事件物件。
官方自定義 eventtarget
我們從官方的事件文件中看到一個簡單的事件驅動物件實現例子。
var eventTarget = function() {
this.listeners = {};
};
eventTarget.prototype.listeners = null;
eventTarget.prototype.addEventListener = function(type, callback) {
if(!(type in this.listeners)) {
this.listeners[type] = [];
}
this.listeners[type].push(callback);
};
eventTarget.prototype.removeEventListener = function(type, callback) {
if(!(type in this.listeners)) {
return;
}
var stack = this.listeners[type];
for(var i = 0, l = stack.length; i < l; i++) {
if(stack[i] === callback){
stack.splice(i, 1);
return this.removeEventListener(type, callback);
}
}
};
eventTarget.prototype.dispatchEvent = function(event) {
if(!(event.type in this.listeners)) {
return;
}
var stack = this.listeners[event.type];
event.target = this;
for(var i = 0, l = stack.length; i < l; i++) {
stack[i].call(this, event);
}
};
這個例子可以是我們實現自己事件的最頂層類。
如何繫結到自定義物件
假設我們已經有了 eventTarget物件,下面我們要做的就是把事件繫結到物件上
var component = function(){};
component.prototype = new eventTarget();
var com_1 = new component();
com_1.addEventListener('click',function(){
console.log('i m listenning click event')
});
com_1.dispatchEvent(new MouseEvent('click')) //i m listenning click event
加入htmlElement事件流
htmlElement的事件流是獨立的,實際上的C++在做事件流的傳遞,自定義的事件沒法加入到這個事件流當中。
這種冒泡的事件流機制非常常見,在ios,Android中的事件驅動都是類似這種機制。在事件中這樣的思想/機制能把事件的耦合度降低。在大型元件的事件邏輯中,加入這種機制個人認為是非常好的。
這裡提供一個實現思路,鄙人愚見,請大神指點。
建立自己的物件的事件流
var flowEvent = function (parentNode) {
this.listeners = {};
this.childrenNode = [];
this.parentNode = parentNode; // 父節點
parentNode && parentNode.childrenNode.push(this) //加入父節點的子節點
}
flowEvent.prototype = new eventTarget(); //繼承事件
flowEvent.prototype.dispatchEvent = function (event) { //重寫事件分發方法
if(!(event.type in this.listeners)) {
return;
}
var stack = this.listeners[event.type]; //要獨立lisenter
event.target = this;
for(var i = 0, l = stack.length; i < l; i++) {
stack[i].call(this, event);
}
if(event.target){
//todo 加入阻止同類事件&阻止傳遞
this.childrenNode.length>0 && this.childrenNode.forEach(function (v) {
v.dispatchEvent(event)
})
}else{
this.parentNode && this.parentNode.dispatchEvent(event)
}
}