Node中不少模塊都可以使用EventEmitter,有了EventEmitter才能方便的進行事件的監聽。下面看一下Node.js中的EventEmitter如何使用。node
EventEmitter是對事件觸發和事件監聽功能的封裝,在node.js中的event模塊中,event模塊只有一個對象就是EventEmitter,下面是一個最基本的使用方法:數組
var EventEmitter = require('events').EventEmitter; var event = new EventEmitter(); event.on('some_event', function() { console.log('some_event 事件觸發'); }); setTimeout(function() { event.emit('some_event'); }, 1000);
上面的代碼中首先實例化了一個EventEimitter對象,而後就能夠進行事件的監聽以及發佈。經過on方法對特定的事件進行監聽,經過emit方法對事件進行發佈。在1s後發佈一個"some_event"事件,這個時候就會自動被event對象經過on進行監聽,並觸發對應的回調方法。app
EventEmitter實例對象支持的方法列表以下:
emitter.on(name, f) //對事件name指定監聽函數f emitter.once(name, f) //與on方法相似,可是監聽函數f是一次性的,使用後自動移除 emitter.listeners(name) //返回一個數組,成員是事件name全部監聽函數 emitter.removeListener(name, f) //移除事件name的監聽函數f emitter.removeAllListeners(name) //移除事件name的全部監聽函數 ......
同時,事件的發佈emit方法能夠傳入多個參數,第一個參數是定義的事件,後面其餘參數回做爲參數傳遞到監聽器的回調函數中。dom
爲了可以更容易理解其內部監聽事件的實現的原理,因此本身封裝了一個模塊以下:
function EventEmitter(){ this._events = {};//初始化一個私有的屬性 } //type 綁定的事件名 // listen 事件發生後的監聽 function EventEmitter(){ this._events = {};//初始化一個私有的屬性 } //type 綁定的事件名 // listen 事件發生後的監聽 EventEmitter.prototype.on = EventEmitter.prototype.addListener= function(type,listener){ if(this._events[type]){ this._events[type].push(listener); }else{ this._events[type] = [listener]; } }; EventEmitter.prototype.once = function(type,listener){ function callOnce() { listener.apply(this, arguments); this.removeListener(type, callOnce); } this.on(type, callOnce); }; EventEmitter.prototype.emit = function(type){ var callbacks = this._events[type]; var args = Array.prototype.slice.call(arguments,1); var self = this; callbacks.forEach(function (callback) { callback.apply(self, args); }) }; EventEmitter.prototype.removeListener=function(type,listener){ if(this._events[type]){ var listeners = this._events[type]; for(var i=0;i<listeners.length;i++){ if(listeners[i] === listener){ listeners.splice(i,1); return; } } } }; module.exports = EventEmitter;
經過如下方式實現:異步
var EventEmitter = require('./EventEmitter'); var util = require('util'); function Girl(name){ this.name = name; EventEmitter.call(this); } util.inherits(Girl,EventEmitter); var girl = new Girl(); function Boy(name){ this.name = name; this.say = function(thing){ console.log(thing); } } var xiaoming = new Boy('小明'); var xiaohua = new Boy('小花'); //註冊監聽 事件 訂閱 girl.addListener('看',xiaoming.say); //註冊監聽 事件 訂閱 girl.on('看',xiaohua.say); //發射事件 發佈 girl.emit('看','鑽石'); girl.removeListener('看',xiaoming.say); //girl.removeAllListeners('看'); girl.emit('看','項鍊');
以下一些關鍵點是須要注意的
eventEmitter.emit() 方法容許將任意參數傳給監聽器函數。 當一個普通的監聽器函數被 EventEmitter 調用時,標準的 this 關鍵詞會被設置指向監聽器所附加的 EventEmitter。函數
const myEmitter = new MyEmitter(); myEmitter.on('event', function(a, b) { console.log(a, b, this); // 打印: // a b MyEmitter { // domain: null, // _events: { event: [Function] }, // _eventsCount: 1, // _maxListeners: undefined } }); myEmitter.emit('event', 'a', 'b');
也可使用 ES6 的箭頭函數做爲監聽器。可是這樣 this 關鍵詞就再也不指向 EventEmitter 實例:oop
const myEmitter = new MyEmitter(); myEmitter.on('event', (a, b) => { console.log(a, b, this); // 打印: a b {} }); myEmitter.emit('event', 'a', 'b');
EventEmitter 會按照監聽器註冊的順序同步地調用全部監聽器。 因此須要確保事件的正確排序且避免競爭條件或邏輯錯誤。 監聽器函數可使用 setImmediate() 或 process.nextTick() 方法切換到異步操做模式:ui
const myEmitter = new MyEmitter(); myEmitter.on('event', (a, b) => { setImmediate(() => { console.log('這個是異步發生的'); }); }); myEmitter.emit('event', 'a', 'b');
當 EventEmitter 實例中發生錯誤時,會觸發一個 'error' 事件。 這在 Node.js 中是特殊狀況。
若是 EventEmitter 沒有爲 'error' 事件註冊至少一個監聽器,則當 'error' 事件觸發時,會拋出錯誤、打印堆棧跟蹤、且退出 Node.js 進程。this
const myEmitter = new MyEmitter(); myEmitter.emit('error', new Error('whoops!')); // 拋出錯誤,並使 Node.js 崩潰
爲了防止 Node.js 進程崩潰,能夠在使用 domain 模塊。 (注意,domain 模塊已被廢棄。)
做爲最佳實踐,應該始終爲 'error' 事件註冊監聽器。spa
const myEmitter = new MyEmitter(); myEmitter.on('error', (err) => { console.error('有錯誤'); }); myEmitter.emit('error', new Error('whoops!')); // 打印: 有錯誤
新增於: v0.1.26 eventName <any> listener <Function> 從名爲 eventName 的事件的監聽器數組中移除指定的 listener。 const callback = (stream) => { console.log('有鏈接!'); }; server.on('connection', callback); // ... server.removeListener('connection', callback); removeListener 最多隻會從監聽器數組裏移除一個監聽器實例。 若是任何單一的監聽器被屢次添加到指定
eventName 的監聽器數組中,則必須屢次調用 removeListener 才能移除每一個實例。
注意,一旦一個事件被觸發,全部綁定到它的監聽器都會按順序依次觸發。 這意味着,在事件觸發後、最後一個監聽器完成執行前,任何 removeListener() 或 removeAllListeners() 調用都不會從 emit() 中移除它們。 隨後的事件會像預期的那樣發生。
const myEmitter = new MyEmitter(); const callbackA = () => { console.log('A'); myEmitter.removeListener('event', callbackB); }; const callbackB = () => { console.log('B'); }; myEmitter.on('event', callbackA); myEmitter.on('event', callbackB); // callbackA 移除了監聽器 callbackB,但它依然會被調用。 // 觸發是內部的監聽器數組爲 [callbackA, callbackB] myEmitter.emit('event'); // 打印: // A // B // callbackB 被移除了。 // 內部監聽器數組爲 [callbackA] myEmitter.emit('event'); // 打印: // A
由於監聽器是使用內部數組進行管理的,因此調用它會改變在監聽器被移除後註冊的任何監聽器的位置索引。 雖然這不會影響監聽器的調用順序,但意味着由 emitter.listeners() 方法返回的監聽器數組副本須要被從新建立。
返回一個 EventEmitter 引用,能夠鏈式調用