事件監聽在前端的開發過程當中是一個很常見的狀況。DOM上的事件監聽方式,讓咱們看到了經過事件的方式來進行具體的業務邏輯的處理的便捷。javascript
在具體的一些業務場景中,第三方的自定義事件可以在層級較多,函數調用困難以及須要多個地方響應的時候有着其獨特的優點——調用方便,避免多層嵌套,下降組件間耦合性。前端
這篇文章所提到的EventEmitter3,就是一個典型的第三方事件庫,可以讓咱們經過自定義的實踐來實現多個函數與組件間的通訊。java
EventEmitter3的設計較爲的簡單,具體結構能夠看下圖所示。git
下面咱們將按照通常人的正常思路來對這個結構進行介紹。github
EE
function EE(fn, context, once) {
this.fn = fn;
this.context = context;
this.once = once || false;
}
複製代碼
從類EE
的代碼中咱們可以很明確的瞭解到,第一個參數爲回調函數,第二個參數爲回調函數的上下文,第三個參數是一個once
的標誌位。因爲代碼簡單,在這裏就簡單介紹下了。api
該方法用於存儲eventEmitter的整個事件名稱與回調函數的集合,初始值爲undefined。數組
on
,區別在於該函數只會觸發一次下面咱們將從添加監聽函數, 事件觸發與刪除監聽函數來進行具體的代碼分析,從而瞭解該庫的實現思路。bash
具體代碼以下所示:app
//一個單一的事件監聽函數的單元
//
// @param {Function} fn Event handler to be called. 回調函數
// @param {Mixed} context Context for function execution. 函數執行上下文
// @param {Boolean} [once=false] Only emit once 是否執行一次的標誌位
// @api private 私有API
function EE(fn, context, once) {
this.fn = fn;
this.context = context;
this.once = once || false;
}
複製代碼
該類爲eventEmitter中用於存儲事件監聽函數的最小類。函數
on
函數具體代碼以下所示:
// Register a new EventListener for the given event.
// 註冊一個指定的事件的事件監聽函數
//
// @param {String} event Name of the event. 事件名
// @param {Function} fn Callback function. 回調函數
// @param {Mixed} [context=this] The context of the function. 上下文
// @api public 公有API
EventEmitter.prototype.on = function on(event, fn, context) {
var listener = new EE(fn, context || this)
, evt = prefix ? prefix + event : event;
if (!this._events) this._events = prefix ? {} : Object.create(null);
if (!this._events[evt]) {
this._events[evt] = listener;//第一次存儲爲一個事件監聽對象
} else {
if (!this._events[evt].fn) {//第三次及之後則直接向對象數組中添加事件監聽對象
this._events[evt].push(listener);
} else {//第二次將存儲的對象與新對象轉換爲事件監聽對象數組
this._events[evt] = [
this._events[evt], listener
];
}
}
return this;
}
複製代碼
當咱們向事件E添加函數F時,會調用on
方法,此時on方法會檢查eventEmitter中prototype屬性events的E屬性。
undefined
時,直接將該函數所在的事件對象賦值給evt屬性。push
方法向數組中添加對象。prefix
是用來判斷Object.create()
方法是否存在,若是存在則直接調用該方法來建立屬性,不然經過在屬性前添加~
來避免覆蓋原有屬性。
once
的函數實現與on
函數基本一致,因此在此就再也不進行分析。
emit
函數代碼以下所示:
// Emit an event to all registered event listeners.
// 觸發已經註冊的事件監聽函數
//
// @param {String} event The name of the event. 事件名
// @returns {Boolean} Indication if we've emitted an event. 若是觸發事件成功,則返回true,不然返回false
// @api public 公有API
EventEmitter.prototype.emit = function emit(event, a1, a2, a3, a4, a5) {
var evt = prefix ? prefix + event : event;
if (!this._events || !this._events[evt]) return false;
var listeners = this._events[evt]
, len = arguments.length
, args
, i;
if ('function' === typeof listeners.fn) {
if (listeners.once) this.removeListener(event, listeners.fn, undefined, true);
switch(len) {
case 1:
return listeners.fn.call(listeners.context), true;
case 2:
return listeners.fn.call(listeners.context, a1), true;
case 3:
return listeners.fn.call(listeners.context, a1, a2), true;
case 4:
return listeners.fn.call(listeners.context, a1, a2, a3), true;
case 5:
return listeners.fn.call(listeners.context, a1, a2, a3, a4), true;
case 6:
return listeners.fn.call(listeners.context, a1, a2, a3, a4, a5), true;
}
for(i = 1, args = new Array(len - 1); i < len; i++) {
args[i - 1] = arguments[i];
}
listeners.fn.apply(listeners.context, args);
} else {
//因爲篇幅緣由省略E屬性爲數組時經過循環調用來實現事件觸發的過程
}
return true;
};
複製代碼
當咱們觸發事件E時,咱們只須要調用emit
方法。該方法會自動檢索事件E中全部的事件監聽對象,觸發全部的事件監聽函數,同時移除掉經過once
添加,只須要觸發一次的事件監聽函數。
removeListener
函數代碼以下:
// Remove event listeners.
// 移除事件監聽函數
//
// @param {String} event The event we want to remove. 須要被移除的事件名
// @param {Function} fn The listener that we need to find. 須要被移除的事件監聽函數
// @param {Mixed} context Only remove listeners matching this context. 只移除匹配該參數指定的上下文的監聽函數
// @param {Boolean} once Only remove once listeners. 只移除匹配該參數指定的once屬性的監聽函數
// @api public 公共API
EventEmitter.prototype.removeListener = function removeListener(event, fn, context, once) {
var evt = prefix ? prefix + event : event;
if (!this._events || !this._events[evt]) return this;
var listeners = this._events[evt]
, events = [];
if (fn) {
if (listeners.fn) {
if (
listeners.fn !== fn
|| (once && !listeners.once)
|| (context && listeners.context !== context)
) {
events.push(listeners);
}
} else {
//因爲篇幅緣由省去便利listeners屬性查找函數刪除的代碼
}
}
//
// Reset the array, or remove it completely if we have no more listeners.
//
if (events.length) {
this._events[evt] = events.length === 1 ? events[0] : events;
} else {
delete this._events[evt];
}
return this;
};
複製代碼
removeListener
函數實現較爲簡單。當咱們須要移除事件E的某個函數時,它使用一個event
屬性來保存不須要被移除的事件監聽對象,而後便利整個事件監聽數組(單個時爲對象),而且最後將event
屬性的值賦值給E屬性從而覆蓋掉原有的屬性,達到刪除的目的。
該庫中還有一些其餘的函數,因爲對整個庫的理解不產生太大影響,所以沒有在此進行講解,有須要的能夠前往個人github倉庫進行查看。
eventEmitter
的代碼雖然結構清晰,可是仍然存在一些問題。例如on
和once
方法的實現中,只有一個屬性不一樣,其他代碼都如出一轍,其實能夠抽出一個特定的函數來進行處理,經過屬性來進行區分調用便可。
同時,在同一個函數例如emit
中,也存在大量的重複代碼,能夠進行進一步的抽象和整理,使得代碼更加簡單。
eventEmitter
第三方事件庫從實現上來看較爲簡單,而且結構清晰容易閱讀,推薦有興趣的能夠花大約一個小時的時間來學習下。