Nodejs 的大部分核心 API 都是基於異步事件驅動設計的,事件驅動核心是經過 node 中 Events 對象來實現事件的發送和監聽回調綁定,咱們經常使用的 stream 模塊也是依賴於 Events 模塊是來實現數據流之間的回調通知,如在數據到來時觸發 data 事件,流對象爲可讀狀態觸發 readable 事件,當數據讀寫完畢後發送 end 事件。node
既然 Events 模塊如此重要,咱們有必要來學習一下 Events 模塊的基本使用,以及如何模擬實現 Events 模塊中經常使用的 APIapi
首先咱們瞭解一下 Events 模塊的基本用法,其實 Events 模塊本質上是觀察者模式的實現,所謂觀察者模式就是:數組
它定義了對象間的一種一對多的關係,讓多個觀察者對象同時監聽某一個主題對象,當一個對象發生改變時,全部依賴於它的對象都將獲得通知bash
觀察者模式有對應的觀察者以及被觀察的對象,在 Events 模塊中,對應的實現就是 on 和 emit 函數app
const EventEmitter = require('events');
class MyEmitter extends EventEmitter {}
const myEmitter = new MyEmitter();
myEmitter.on('嗨', (str) => {
console.log(str);
});
myEmitter.emit('嗨','你好');
複製代碼
從上述的使用中,咱們能夠知道 on 是用來監聽事件的發生,而 emit 是用來觸發事件的發生,一旦 emit 觸發了事件,on 就會被通知到,從而執行對應的回調函數。異步
有了這個實例,咱們能夠思考下如何實現這個 EventEmitter 類。函數
思路:當咱們執行 on 函數時,咱們能夠將回調函數保存起來,等到 emit 觸發了事件時,將回調函數拿出來執行,那麼就能夠實現了事件的監聽以及訂閱了。學習
class EventEmitter{
constructor(){
#事件監聽函數保存的地方
this.events={};
}
on(eventName,listener){
if (this.events[eventName]) {
this.events[eventName].push(listener);
} else {
#若是沒有保存過,將回調函數保存爲數組
this.events[eventName] = [listener];
}
}
emit(eventName){
#emit觸發事件,把回調函數拉出來執行
this.events[eventName] && this.events[eventName].forEach(listener => listener())
}
}
複製代碼
上述就實現了一個簡單的 EventEmitter 類,下面來實例一下:ui
let event = new EventEmitter();
event.on('嗨',function(){
console.log('你好');
});
event.emit('嗨');
#輸出:你好
複製代碼
完善:咱們注意到在原生的 EventEmitter 類中,emit 是能夠傳遞參數到咱們的回調函數中,那麼咱們實現的類也應該支持傳遞參數。咱們對 emit 進行以下更改this
emit(eventName,...rest){
#emit觸發事件,把回調函數拉出來執行
this.events[eventName] && this.events[eventName].forEach(listener => listener.apply(this,rest))
}
複製代碼
完善以後,從新實例化,以下:
let event = new EventEmitter();
event.on('嗨',function(str){
console.log(str);
});
event.emit('嗨','你好');
#輸出:你好
複製代碼
Events 模塊中除了 on、emit 函數以外,還包含了不少經常使用的 api,咱們一一來介紹幾個實用的 api
API名稱 | API方法描述 |
---|---|
addListener(eventName, listener) | on(eventName, listener)別名,爲指定事件添加一個監聽器到監聽器數組的尾部 |
removeListener(eventName, listener) | 從名爲 eventName 的事件的監聽器數組中移除指定的 listener |
removeAllListeners(eventName, listener) | 移除所有監聽器或指定的 eventName 事件的監聽器 |
once(eventName, listener) | 添加單次監聽器 listener 到名爲 eventName 的事件 |
listeners(eventName) | 返回名爲 eventName 的事件的監聽器數組的副本 |
setMaxListeners(n) | 能夠爲指定的 EventEmitter 實例修改監聽器數量限制 |
在 Events 模塊中,addListener 與 on 方法的使用是完成相同的,只是名字不一樣,咱們能夠經過原型來給兩個函數創建相等關聯
EventEmitter.prototype.addListener=EventEmitter.prototype.on
複製代碼
removeListener 方法能夠從指定名字的監聽器數組中移除指定的 listener,這樣的話,當再次 emit 事件的時候,不會觸發 on 綁定的回調函數,以下:
const EventEmitter = require('events');
class MyEmitter extends EventEmitter {}
const myEmitter = new MyEmitter();
let callback = (str) => {
console.log(str);
}
myEmitter.on('嗨', callback);
myEmitter.emit('嗨','你好');#輸出:你好
myEmitter.removeListener('嗨',callback);
myEmitter.emit('嗨','你好');#無輸出
複製代碼
實現思路:咱們只要在執行 removeListener 函數的時候,將先前保存的回調函數去除掉便可
removeListener (eventName,listener) {
#保證回調函數數組存在,同時去除指定的listener
this.events[eventName] && this.events[eventName] = this.events[eventName].filter(l => l != listener);
}
複製代碼
同時 removeListener 與 off 方法也是功能徹底相同,只是命名不一樣,所以能夠經過以下方法賦值:
EventEmitter.prototype.removeListener=EventEmitter.prototype.off
複製代碼
removeAllListeners 移除所有監聽器或指定的 eventName 事件的監聽器,其實 removeAllListeners 就包含了 removeListener 的功能,只是 removeListener 只能指定特定的監聽器,removeAllListeners 能夠移除所有監聽器。 實現思路:在執行 removeAllListeners,將全部的回調函數都給去除便可
removeAllListeners (eventName) {
#移除所有監聽器
delete this.events[eventName]
}
複製代碼
once 方法的描述是添加單次監聽器 listener 到名爲 eventName 的事件,其實就是經過 once 添加的監聽器,只能執行一次,執行一次以後就會被銷燬,不能再次執行
const EventEmitter = require('events');
class MyEmitter extends EventEmitter {}
const myEmitter = new MyEmitter();
myEmitter.once('嗨', (str) => {
console.log(str);
});
myEmitter.emit('嗨','你好');
myEmitter.emit('嗨','你好');
myEmitter.emit('嗨','你好'); #只能輸出一次你好
複製代碼
實現思路:當 once 監聽的事件回調函數執行以後,經過 removeListener 將事件監聽器給解綁掉,那麼事件再次被 emit 的時候,就不會再次執行回調,這樣就能保證事件回調只能執行一次
once (eventName, listener) {
#從新改變監聽回調函數,使其執行以後能夠被銷燬
let reListener = (...rest) => {
listener.apply(this,rest);
#執行完以後解除事件綁定
this.removeListener(type,wrapper);
}
this.on(eventName,reListener);
}
複製代碼
listeners 方法返回名爲 eventName 的事件的監聽器數組的副本,其實就是獲取 eventName 中全部的回調函數,這個實現起來很容易,就很少贅述了,代碼以下:
listeners (eventName) {
return this.events[eventName]
}
複製代碼
默認狀況下,若是爲特定事件添加了超過 10 個監聽器,則 EventEmitter 會打印一個警告。 這有助於發現內存泄露, 可是,並非全部的事件都要限制 10 個監聽器。 emitter.setMaxListeners() 方法能夠爲指定的 EventEmitter 實例修改限制。 值設爲 Infinity(或 0)表示不限制監聽器的數量。
const EventEmitter = require('events');
class MyEmitter extends EventEmitter {}
const myEmitter = new MyEmitter();
let callback = (str) => {
console.log(str);
}
for (let i = 0; i <= 11; i++) {
myEmitter.on('嗨', callback);
}
myEmitter.emit('嗨', '你好');
複製代碼
輸入結果如圖:
constructor(){
#事件監聽函數保存的地方
this.events={};
#最大監聽器數量
this._maxListeners = 10;
}
複製代碼
on(eventName,listener){
if (this.events[eventName]) {
this.events[eventName].push(listener);
#若是超過最大限度,以及不爲0,則做出內存泄漏提示
if (this._maxListeners != 0 && this.events[type].length >= this._maxListeners) {
console.error('超過最大的監聽數量可能會致使內存泄漏');
}
} else {
#若是沒有保存過,將回調函數保存爲數組
this.events[eventName] = [listener];
}
}
複製代碼
setMaxListeners(MaxListeners) {
this._maxListeners = MaxListeners
}
複製代碼
本文從 node 的 Events 模塊出發,而後去介紹了 Events 模塊經常使用 API 的使用,從中經過一步一步簡易去思考這些 API 使用的內部原理,簡易的實現了這些 API,但願你們看完文章以後,能對 Events 模塊有進一步的理解。