本篇文章帶大家了解一下Node.js中的Events模塊,介紹一下 Events 中的發(fā)布訂閱模式,希望對(duì)大家有所幫助!
Events模塊
參考官網(wǎng):events 事件觸發(fā)器 | Node.js
http://nodejs.cn/api/events.html
Events
模塊是Node最重要的模塊,它提供了一個(gè)屬性 EventEmitter
,EventEmitter
的核心是事件發(fā)射與事件監(jiān)聽器。
Node中大部分的模塊,都繼承自 Events
模塊。
Events
模塊是Node對(duì) 發(fā)布訂閱模式(publish/subscribe
)的實(shí)現(xiàn)。一個(gè)對(duì)象通過這個(gè)模塊,向另一個(gè)對(duì)象傳遞消息。- 該模塊通過
EventEmitter
屬性,提供了一個(gè)構(gòu)造函數(shù)。該構(gòu)造函數(shù)的實(shí)例具有on
方法,可以用來監(jiān)聽指定事件,并觸發(fā)回調(diào)函數(shù)。 - 任意對(duì)象都可以發(fā)布指定事件,被
EventEmitter
實(shí)例的on方法監(jiān)聽到。
發(fā)布訂閱模式
關(guān)于 發(fā)布訂閱模式 ,可以參考我之前的博客文章。
關(guān)于 Events
中的發(fā)布訂閱模式,我們要先清楚它的幾個(gè)常用方法。
- 訂閱方法:
on
方法用來訂閱事件,訂閱是將方法對(duì)應(yīng)成一種一對(duì)多的關(guān)系。 - 發(fā)布方法:
emit
用來執(zhí)行訂閱的事件。 - 取消訂閱:
off
方法可以移除對(duì)應(yīng)的事件監(jiān)聽。 - 訂閱一次:
once
綁定事件當(dāng)執(zhí)行后自動(dòng)刪除訂閱的事件。
on 和 emit
on
方法的第一個(gè)參數(shù)用來設(shè)定類名,第二個(gè)參數(shù)也是一個(gè)函數(shù),里面可以接收發(fā)布時(shí)傳入的參數(shù)。
emit
方法第一個(gè)參數(shù)是類名,之后的參數(shù)都是傳入 on
方法函數(shù)中的參數(shù)。
on
和 emit
具體應(yīng)用可以參考下面這個(gè)簡(jiǎn)單的Demo。
const EventEmitter = require('events'); // 自定義一個(gè) 構(gòu)造函數(shù) function Cat() {} // 原型繼承 需要通過實(shí)例來調(diào)用繼承方法 Object.setPrototypeOf(Cat.prototype, EventEmitter.prototype); let cat = new Cat(); const sleep = (a, b) => { console.log(a, '睡'); }; const eat = (a, b) => { console.log(b, '吃'); }; cat.on('貓咪', sleep) cat.on('貓咪', eat) setTimeout(() => { // 小胡子 吃 // 小胖仙 睡 cat.emit('貓咪', '小胖仙', '小胡子') }, 1000);
現(xiàn)在我們可以實(shí)現(xiàn)一套 on
和 emit
方法。
function EventEmitter() { this._event = {} } // on 方法 EventEmitter.prototype.on = function (eventName, callBack) { if (!this._event) { this._event = {} } if (this._event[eventName]) { this._event[eventName].push(callBack) // 相當(dāng)于 {eventName:[fn1,fn2]} } else { this._event[eventName] = [callBack]; // 相當(dāng)于 {eventName:[fn1]} } } // emit 方法 EventEmitter.prototype.emit = function (eventName, ...args) { this._event[eventName].forEach(fn => { fn(...args) }); }
off
off
方法的第一個(gè)參數(shù)用來設(shè)定類名,第二個(gè)參數(shù)傳入需要被移除的函數(shù)回調(diào)。
// ... setTimeout(() => { // 小胡子 吃 // 小胖仙 睡 cat.emit('貓咪', '小胖仙', '小胡子') cat.off('貓咪', sleep); // 小胡子 吃 cat.emit('貓咪', '小胖仙', '小胡子') }, 1000);
這樣我們可以大概判斷出來,移除掉和我們傳入函數(shù)相同的函數(shù),我們很快想到 filter 方法。
// off 方法 EventEmitter.prototype.off = function (eventName, callBack) { if (this._event && this._event[eventName]) { this._event[eventName] = this._event[eventName].filter( fn => fn !== callBack && fn.c !== callBack // fn.c參考下面的once方法實(shí)現(xiàn) ) } }
once
once
方法的第一個(gè)參數(shù)用來設(shè)定類名,第二個(gè)參數(shù)傳入只需要執(zhí)行一次的函數(shù)回調(diào)。
// ... const demolition =() => { console.log('拆家'); } cat.once('貓咪', demolition) setTimeout(() => { // ...... 拆家 cat.emit('貓咪', '小胖仙', '小胡子') }, 1000);
這樣我們可以根據(jù)之前實(shí)現(xiàn)的 on
和 off
來實(shí)現(xiàn)此方法。
// once 方法 EventEmitter.prototype.once = function (eventName, callBack) { const one = () => { callBack(); this.off(eventName, one); } this.on(eventName, one); }
看起來這個(gè)方法好像沒有什么問題,執(zhí)行起來也全都是正確的。
但是在一種特殊情況下的時(shí)候,還是出現(xiàn)了錯(cuò)誤。
那種情況就是如果我們?cè)趫?zhí)行 once
方法之前,就已經(jīng)通過 off
方法將其移除了。
我們實(shí)現(xiàn)的方法就不能實(shí)現(xiàn)這個(gè)需求了,所以我們還需要對(duì) once
方法進(jìn)行一些修改 ( off
方法已經(jīng)處理過了)。
添加一個(gè)自定義屬性,用來對(duì)函數(shù)進(jìn)行 “緩存” 。
EventEmitter.prototype.once = function (eventName, callBack) { const one = () => { // ... } one.c = callBack; // 自定義一個(gè)屬性 // ... }
這樣我們就實(shí)現(xiàn)了 once
方法。