事件在js中很是的常見,無論是瀏覽器仍是node,這種事件發佈/訂閱模式的應用都是很常見的。至於發佈/訂閱模式和觀察者模式是不是同一種設計模式說法都有,這裏不作具體的討論。在以前的項目中也曾本身實現過一個事件模塊,核心仍是一個EventEmitter。下文就要結合node中的event模塊分析一下,一個EventEmitter應該如何實現,有什麼注意點。node
首先第一步就是一個EventEmitter的類,而後考慮一下這個類的實例屬性和實例方法。
實例屬性的話最基礎的就是一個eventMap,能夠是一個空對象,固然也能夠這樣建立Object.create(null)。若是須要還能夠增長maxListener之類的屬性。
實例方法的話,最核心的就是add delete emit分別是添加事件,刪除事件,發佈事件。固然實際實現的時候,例如count,has,once(一次性添加),preAdd(添加在事件隊列最前面),這些方法則是能夠根據實際需求去添加。設計模式
如下代碼均爲簡化的僞代碼瀏覽器
EventEmitter.prototype.add = function(type, fn) { if (!isFunction(fn)) return;//判斷是否在監聽中添加的是合法的函數 //判斷type是否添加過,添加過一個仍是多個函數 if (this.event[type]) { if (isArray(this.event[type])){ //若是想要實現preadd將push改成unshift便可 this.event[type].push(fn); } else { //若是想要實現preadd改變順序 this.event[type] = [this.event[type], fn]; } } else { this.event[type] = fn; } }
參考一下node的once方法app
function onceWrapper(...args) { if (!this.fired) { this.target.removeListener(this.type, this.wrapFn); this.fired = true; Reflect.apply(this.listener, this.target, args); } } function _onceWrap(target, type, listener) { var state = { fired: false, wrapFn: undefined, target, type, listener }; var wrapped = onceWrapper.bind(state); wrapped.listener = listener; state.wrapFn = wrapped; return wrapped; } EventEmitter.prototype.once = function once(type, listener) { this.on(type, _onceWrap(this, type, listener)); return this; };
函數用onceWrap包裹,運行前須要對添加的監聽進行移除dom
很簡單理清楚幾種邊界狀況就能夠了函數
EventEmitter.prototype.delete = function(type, fn) { //直接刪除整類監聽 if(fn === undefined){ this.events[type] && delete this.events[type]; }else{ //判斷fn合法性就省了 if(this.events[type]) { if (this.events[type] === fn) { delete this.events[type]; } else { for (var i in this.events[type]) { if(this.events[type][i] === fn){ if (i === 0) { this.events[type].shift(); } else { this.events[type].splice(i,1); } } } if(this.events[type].length === 1) this.events[type] = this.events[type][0]; } } } }
EventEmitter.prototype.emit = function(type) { //獲取參數 var args = [].slice.call(arguments, 1); var handler = events[type]; if (handler === undefined) return false; if (typeof handler === 'function') { handle.apply(this, args); } else { var len = handler.length; const listeners = arrayClone(handler, len); for (var i = 0; i < len; ++i) handle[i].apply(this, args); } }
發佈事件有兩個注意點,一個是注意參數的保留,另外一個則是上下文,這裏上下文是直接取了event實例的上下文,也能夠考慮一下手動傳入上下文的形式,或者說fn在定義的時候直接寫成箭頭函數,也能夠避免上下文成爲eventEmit實例的上下文。學習
這裏提一下node的event的錯誤事件this
當 EventEmitter 實例中發生錯誤時,會觸發一個 'error' 事件。 這在 Node.js 中是特殊狀況。
若是 EventEmitter 沒有爲 'error' 事件註冊至少一個監聽器,則當 'error' 事件觸發時,會拋出錯誤、打印堆棧跟蹤、且退出 Node.js 進程。
爲了防止 Node.js 進程崩潰,能夠在 process 對象的 uncaughtException 事件上註冊監聽器,或使用 domain 模塊。 (注意,domain 模塊已被廢棄。)
做爲最佳實踐,應該始終爲 'error' 事件註冊監聽器。
若是有須要在本身的實踐中也能夠增長一個錯誤處理的機制,保證event實例的穩定性prototype
一個事件訂閱發佈類其實不難實現,而在node中有不少厲害的類都是繼承的事件類,而以後我會接着對node文件系統進行學習設計