【滲透】關於Node.js的事件訂閱發佈原理

1、Node的事件訂閱發佈

1.EventEmitter

Node中不少模塊都可以使用EventEmitter,有了EventEmitter才能方便的進行事件的監聽。下面看一下Node.js中的EventEmitter如何使用。node

(1) 基本使用

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

(2)EventEmitter支持的方法

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

2.自定義EventEmitter

爲了可以更容易理解其內部監聽事件的實現的原理,因此本身封裝了一個模塊以下:

clipboard.png

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('看','項鍊');

3.NodeJs的events事件

以下一些關鍵點是須要注意的

(1)給監聽器傳入參數與 this

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');

(2)異步與同步

EventEmitter 會按照監聽器註冊的順序同步地調用全部監聽器。 因此須要確保事件的正確排序且避免競爭條件或邏輯錯誤。 監聽器函數可使用 setImmediate() 或 process.nextTick() 方法切換到異步操做模式:ui

const myEmitter = new MyEmitter();
myEmitter.on('event', (a, b) => {
  setImmediate(() => {
    console.log('這個是異步發生的');
  });
});
myEmitter.emit('event', 'a', 'b');

(3)錯誤事件

當 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!'));
// 打印: 有錯誤

(4)emitter.removeListener

新增於: 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 引用,能夠鏈式調用

相關文章
相關標籤/搜索