Events模塊是Node對(duì)“發(fā)布/訂閱”模式(publish/subscribe)的實(shí)現(xiàn),一個(gè)對(duì)象通過(guò)這個(gè)模塊,向另一個(gè)對(duì)象傳遞消息,幾乎所有常用的node模塊都繼承了events模塊,比如http、fs等。
Node中的Event模塊僅僅提供了一個(gè)對(duì)象: EventEmitter, EventEmitter 的核心就是事件觸發(fā)與事件監(jiān)聽(tīng)器功能的封裝。
1、訂閱發(fā)布模式(Subscribe/Publish)
訂閱發(fā)布模式(又稱事件監(jiān)聽(tīng)器模式)廣泛用于異步編程中。events模塊是訂閱發(fā)布模式的一個(gè)簡(jiǎn)單實(shí)現(xiàn)。訂閱發(fā)布模式定義了一種一對(duì)多的依賴關(guān)系,在Node中EventEmitter 對(duì)象上開(kāi)放了一個(gè)可以用于監(jiān)聽(tīng)的on(eventName,callback)函數(shù),允許將一個(gè)或多個(gè)函數(shù)綁定到對(duì)應(yīng)的事件上。當(dāng) EventEmitter 對(duì)象觸發(fā)一個(gè)事件時(shí),所有綁定在該事件上的函數(shù)都被同步地調(diào)用!
2、Events的API

大多數(shù)時(shí)候我們不會(huì)直接使用 EventEmitter,而是在對(duì)象中繼承它。包括 fs、net、 http 在內(nèi)的,只要是支持事件響應(yīng)的核心模塊都是 EventEmitter 的子類。
原因有兩點(diǎn):
- 具有某個(gè)實(shí)體功能的對(duì)象實(shí)現(xiàn)事件符合語(yǔ)義, 事件的監(jiān)聽(tīng)和發(fā)射應(yīng)該是一個(gè)對(duì)象的方法。
- JavaScript 的對(duì)象機(jī)制是基于原型的,支持 部分多重繼承,繼承 EventEmitter 不會(huì)打亂對(duì)象原有的繼承關(guān)系。
3、方法
3.1 創(chuàng)建監(jiān)聽(tīng)器
-
emitter.on(eventName, listener):添加 listener 函數(shù)到名為 eventName 的事件的監(jiān)聽(tīng)器數(shù)組的末尾。 不會(huì)檢查 listener 是否已被添加。 多次調(diào)用并傳入相同的 eventName 與 listener 會(huì)導(dǎo)致 listener 會(huì)被添加多次。 -
emitter.addListener(eventName, listener):emitter.on(eventName, listener)的別名。
//引入events模塊
const EventEmitter = require('events');
let count = 0;
//創(chuàng)建一個(gè)新實(shí)例
const myEmitter = new EventEmitter();
//給“去逛街”創(chuàng)建一個(gè)監(jiān)聽(tīng)
myEmitter.on('去逛街', () => {
console.log(`買(mǎi)了${++count}件衣服`);
});
// 再次給“去逛街”創(chuàng)建一個(gè)監(jiān)聽(tīng),不會(huì)檢查 listener 是否已被添加,依然被放到監(jiān)聽(tīng)器數(shù)組后面。
myEmitter.on('去逛街', () => {
console.log(`買(mǎi)了${++count}條褲子`);
});
//觸發(fā)監(jiān)聽(tīng)“去逛街”這個(gè)事件
myEmitter.emit('去逛街');
myEmitter.emit('去逛街');
/** 輸出結(jié)果:
* 買(mǎi)了1件衣服
* 買(mǎi)了2條褲子
* 買(mǎi)了3件衣服
* 買(mǎi)了4條褲子*/
-
emitter.once(eventName, listener):添加單次監(jiān)聽(tīng)器 listener 到名為 eventName 的事件。 當(dāng) eventName 事件下次觸發(fā)時(shí),監(jiān)聽(tīng)器會(huì)先被移除,然后再調(diào)用。
//引入events模塊
const EventEmitter = require('events');
let count = 0;
//創(chuàng)建一個(gè)新實(shí)例
const myEmitter = new EventEmitter();
//給“去逛街”創(chuàng)建一個(gè)監(jiān)聽(tīng)
myEmitter.once('去逛街', () => {
console.log(`買(mǎi)了${++count}件衣服`);
});
//觸發(fā)監(jiān)聽(tīng)“去逛街”這個(gè)事件
myEmitter.emit('去逛街'); // 買(mǎi)了1件衣服
myEmitter.emit('去逛街'); // 沒(méi)有被觸發(fā)
-
emitter.prependListener(eventName, listener):添加 listener 函數(shù)到名為 eventName 的事件的監(jiān)聽(tīng)器數(shù)組的開(kāi)頭。 不會(huì)檢查 listener 是否已被添加。 多次調(diào)用并傳入相同的 eventName 和 listener 會(huì)導(dǎo)致 listener 被添加多次。 -
emitter.prependOnceListener(eventName, listener):添加單次監(jiān)聽(tīng)器 listener 到名為 eventName 的事件的監(jiān)聽(tīng)器數(shù)組的開(kāi)頭。 當(dāng) eventName 事件下次觸發(fā)時(shí),監(jiān)聽(tīng)器會(huì)先被移除,然后再調(diào)用。
//引入events模塊
const EventEmitter = require('events');
//創(chuàng)建一個(gè)新實(shí)例
const myEmitter = new EventEmitter();
//給“去逛街”創(chuàng)建一個(gè)監(jiān)聽(tīng)
myEmitter.on('去逛街', () => {
console.log(`買(mǎi)衣服`);
});
// 添加 listener 函數(shù)到名為 eventName 的事件的監(jiān)聽(tīng)器數(shù)組的開(kāi)頭。
myEmitter.prependListener('去逛街', () => {
console.log(`買(mǎi)褲子`);
});
// 添加單次監(jiān)聽(tīng)器 listener 到名為 eventName 的事件的監(jiān)聽(tīng)器數(shù)組的開(kāi)頭
myEmitter.prependOnceListener('去逛街', () => {
console.log(`買(mǎi)鞋子`);
});
//觸發(fā)監(jiān)聽(tīng)“去逛街”這個(gè)事件
myEmitter.emit('去逛街'); // 輸出結(jié)果:買(mǎi)鞋子 買(mǎi)褲子 買(mǎi)衣服
myEmitter.emit('去逛街'); // 輸出結(jié)果:買(mǎi)褲子 買(mǎi)衣服
3.2 調(diào)用監(jiān)聽(tīng)器
-
emitter.emit(eventName[, ...args]):按照監(jiān)聽(tīng)器注冊(cè)的順序,同步地調(diào)用每個(gè)注冊(cè)到名為 eventName 的事件的監(jiān)聽(tīng)器,并傳入提供的參數(shù)。
3.3 移除監(jiān)聽(tīng)器
-
emitter.removeListener(eventName, listener):從名為 eventName 的事件的監(jiān)聽(tīng)器數(shù)組中移除指定的 listener。每次只會(huì)從監(jiān)聽(tīng)器數(shù)組中移除一個(gè)監(jiān)聽(tīng)器。 如果監(jiān)聽(tīng)器被多次添加到指定 eventName 的監(jiān)聽(tīng)器數(shù)組中,則必須多次調(diào)用 removeListener() 才能移除所有實(shí)例。 -
emitter.removeAllListeners([eventName]):移除全部監(jiān)聽(tīng)器或指定的 eventName 事件的監(jiān)聽(tīng)器。 -
emitter.off(eventName, listener):emitter.removeListener()的別名。
//引入events模塊
const EventEmitter = require('events');
//創(chuàng)建一個(gè)新實(shí)例
const myEmitter = new EventEmitter();
const buyClothes = () => {
console.log(`買(mǎi)衣服`);
myEmitter.removeListener('去逛街', buyPants);
}
const buyPants = () => {
console.log(`買(mǎi)褲子`);
}
myEmitter.on('去逛街', buyClothes);
myEmitter.on('去逛街', buyPants);
// buyClothes 移除了監(jiān)聽(tīng)器 buyPants,但它依然會(huì)被調(diào)用。
// 觸發(fā)時(shí)內(nèi)部的監(jiān)聽(tīng)器數(shù)組為 [buyClothes, buyPants]
myEmitter.emit('去逛街'); // 買(mǎi)衣服 買(mǎi)褲子
// buyPants 現(xiàn)已被移除。
// 內(nèi)部的監(jiān)聽(tīng)器數(shù)組為 [buyClothes]
myEmitter.emit('去逛街'); // 買(mǎi)衣服
4、事件
- 'newListener' 事件: EventEmitter 實(shí)例在新的監(jiān)聽(tīng)器被添加到其內(nèi)部監(jiān)聽(tīng)器數(shù)組之前,會(huì)觸發(fā)自身的 'newListener' 事件。在 'newListener' 回調(diào)中注冊(cè)到相同 eventName的任何其他監(jiān)聽(tīng)器將插入到正在添加的監(jiān)聽(tīng)器之前。
const EventEmitter = require('events');
const myEmitter = new EventEmitter();
// EventEmitter 實(shí)例會(huì)在一個(gè)監(jiān)聽(tīng)器被添加到其內(nèi)部監(jiān)聽(tīng)器數(shù)組之前觸發(fā)自身的 'newListener' 事件;
// 只處理一次,避免無(wú)限循環(huán)。
myEmitter.once('newListener', (event, listener) => {
if (event === '去逛街') {
console.log('逛街前的準(zhǔn)備');
myEmitter.on('去逛街', () => {
console.log('涂防曬霜');
});
}
});
myEmitter.on('去逛街', () => {
console.log('買(mǎi)護(hù)膚品');
});
myEmitter.emit('去逛街'); // 逛街前的準(zhǔn)備 涂防曬霜 買(mǎi)護(hù)膚品
myEmitter.emit('去逛街'); // 涂防曬霜 買(mǎi)護(hù)膚品
注意:對(duì)'newListener' 事件的監(jiān)聽(tīng)要放在普通監(jiān)聽(tīng)前面。如下'newListener' 事件不起作用。
const EventEmitter = require('events');
const myEmitter = new EventEmitter();
myEmitter.on('去逛街', () => {
console.log('買(mǎi)護(hù)膚品');
});
myEmitter.once('newListener', (event, listener) => {
if (event === '去逛街') {
console.log('逛街前的準(zhǔn)備');
myEmitter.on('去逛街', () => {
console.log('涂防曬霜');
});
}
});
myEmitter.emit('去逛街'); // 買(mǎi)護(hù)膚品
myEmitter.emit('去逛街'); // 買(mǎi)護(hù)膚品
- 'removeListener' 事件:'removeListener' 事件在 listener 被移除后觸發(fā)
const EventEmitter = require('events');
const myEmitter = new EventEmitter();
const buySkinProd = () => {
console.log('買(mǎi)護(hù)膚品');
myEmitter.removeListener('去逛街', buySkinProd);
}
myEmitter.once('removeListener', (event, listener) => {
if (event === '去逛街') {
console.log('逛街結(jié)束');
}
});
myEmitter.on('去逛街', buySkinProd);
myEmitter.emit('去逛街'); // 買(mǎi)護(hù)膚品 逛街結(jié)束
5、自定義的類繼承 events
// blog.js
const EventEmitter=require('events');
class Base extends EventEmitter {
constructor() {
super();
}
onEvent(eventName,callback){
super.on(eventName,callback);
}
emitEvent(eventName,arg){
super.emit(eventName,arg);
}
};
class BlogInfo extends Base {
constructor() {
super();
}
onSave() {
super.onEvent('saveStart',function(blog){
console.log('saveStart',blog);
});
super.onEvent('blogCount',function(blog){
console.log('blogCount',blog.length);
});
super.onEvent('saveEnd',function(blog){
console.log('saveEnd',blog);
});
}
emitEvent(blog) {
super.emitEvent('saveStart',blog);
super.emitEvent('blogCount',blog);
super.emitEvent('saveEnd',blog);
}
}
exports.blogSave=function(newblog){
console.log(BlogInfo.__proto__.__proto__ === EventEmitter); // true
console.log(BlogInfo.__proto__ === Base); // true
const blogInfo=new BlogInfo();
blogInfo.onSave(newblog);
blogInfo.emitEvent(newblog);
};
// index.js
const http = require('http');
const blog = require('./blog');
const serve = http.createServer((req, res) => {
if (req.url === '/') {
const newblog = {title: "標(biāo)題", content: "內(nèi)容"};
blog.blogSave(newblog);
res.writeHead(200, {'Content-Type': 'text/html;charset=utf-8'});
res.write('<html><body>');
res.write('<h2>Hello World!</h2>');
res.end('</body></html>');
}
});
serve.listen(8000);
console.log('listen 8000');
/** 運(yùn)行index.js結(jié)果
* listen 8000
* true
* true
* saveStart [ { title: '標(biāo)題', content: '內(nèi)容' } ]
* saveEnd [ { title: '標(biāo)題', content: '內(nèi)容' } ]
*/
參考文章:
https://www.jb51.net/article/124799.htm
http://www.itdecent.cn/p/fd1f8c998a2c
http://www.itdecent.cn/p/152fddf0628c