Events模塊是Node對“發(fā)布/訂閱”模式(publish/subscribe)的實現(xiàn),一個對象通過這個模塊蠕嫁,向另一個對象傳遞消息蚜点,幾乎所有常用的node模塊都繼承了events模塊,比如http拌阴、fs等绍绘。
Node中的Event模塊僅僅提供了一個對象: EventEmitter, EventEmitter 的核心就是事件觸發(fā)與事件監(jiān)聽器功能的封裝。
1迟赃、訂閱發(fā)布模式(Subscribe/Publish)
訂閱發(fā)布模式(又稱事件監(jiān)聽器模式)廣泛用于異步編程中陪拘。events模塊是訂閱發(fā)布模式的一個簡單實現(xiàn)。訂閱發(fā)布模式定義了一種一對多的依賴關(guān)系,在Node中EventEmitter 對象上開放了一個可以用于監(jiān)聽的on(eventName,callback)函數(shù),允許將一個或多個函數(shù)綁定到對應的事件上纤壁。當 EventEmitter 對象觸發(fā)一個事件時,所有綁定在該事件上的函數(shù)都被同步地調(diào)用左刽!
2、Events的API
大多數(shù)時候我們不會直接使用 EventEmitter酌媒,而是在對象中繼承它欠痴。包括 fs、net秒咨、 http 在內(nèi)的喇辽,只要是支持事件響應的核心模塊都是 EventEmitter 的子類。
原因有兩點:
- 具有某個實體功能的對象實現(xiàn)事件符合語義雨席, 事件的監(jiān)聽和發(fā)射應該是一個對象的方法菩咨。
- JavaScript 的對象機制是基于原型的,支持 部分多重繼承,繼承 EventEmitter 不會打亂對象原有的繼承關(guān)系抽米。
3特占、方法
3.1 創(chuàng)建監(jiān)聽器
-
emitter.on(eventName, listener)
:添加 listener 函數(shù)到名為 eventName 的事件的監(jiān)聽器數(shù)組的末尾。 不會檢查 listener 是否已被添加云茸。 多次調(diào)用并傳入相同的 eventName 與 listener 會導致 listener 會被添加多次是目。 -
emitter.addListener(eventName, listener)
:emitter.on(eventName, listener)的別名。
//引入events模塊
const EventEmitter = require('events');
let count = 0;
//創(chuàng)建一個新實例
const myEmitter = new EventEmitter();
//給“去逛街”創(chuàng)建一個監(jiān)聽
myEmitter.on('去逛街', () => {
console.log(`買了${++count}件衣服`);
});
// 再次給“去逛街”創(chuàng)建一個監(jiān)聽标捺,不會檢查 listener 是否已被添加胖笛,依然被放到監(jiān)聽器數(shù)組后面。
myEmitter.on('去逛街', () => {
console.log(`買了${++count}條褲子`);
});
//觸發(fā)監(jiān)聽“去逛街”這個事件
myEmitter.emit('去逛街');
myEmitter.emit('去逛街');
/** 輸出結(jié)果:
* 買了1件衣服
* 買了2條褲子
* 買了3件衣服
* 買了4條褲子*/
-
emitter.once(eventName, listener)
:添加單次監(jiān)聽器 listener 到名為 eventName 的事件宜岛。 當 eventName 事件下次觸發(fā)時,監(jiān)聽器會先被移除功舀,然后再調(diào)用萍倡。
//引入events模塊
const EventEmitter = require('events');
let count = 0;
//創(chuàng)建一個新實例
const myEmitter = new EventEmitter();
//給“去逛街”創(chuàng)建一個監(jiān)聽
myEmitter.once('去逛街', () => {
console.log(`買了${++count}件衣服`);
});
//觸發(fā)監(jiān)聽“去逛街”這個事件
myEmitter.emit('去逛街'); // 買了1件衣服
myEmitter.emit('去逛街'); // 沒有被觸發(fā)
-
emitter.prependListener(eventName, listener)
:添加 listener 函數(shù)到名為 eventName 的事件的監(jiān)聽器數(shù)組的開頭。 不會檢查 listener 是否已被添加辟汰。 多次調(diào)用并傳入相同的 eventName 和 listener 會導致 listener 被添加多次列敲。 -
emitter.prependOnceListener(eventName, listener)
:添加單次監(jiān)聽器 listener 到名為 eventName 的事件的監(jiān)聽器數(shù)組的開頭。 當 eventName 事件下次觸發(fā)時帖汞,監(jiān)聽器會先被移除戴而,然后再調(diào)用。
//引入events模塊
const EventEmitter = require('events');
//創(chuàng)建一個新實例
const myEmitter = new EventEmitter();
//給“去逛街”創(chuàng)建一個監(jiān)聽
myEmitter.on('去逛街', () => {
console.log(`買衣服`);
});
// 添加 listener 函數(shù)到名為 eventName 的事件的監(jiān)聽器數(shù)組的開頭翩蘸。
myEmitter.prependListener('去逛街', () => {
console.log(`買褲子`);
});
// 添加單次監(jiān)聽器 listener 到名為 eventName 的事件的監(jiān)聽器數(shù)組的開頭
myEmitter.prependOnceListener('去逛街', () => {
console.log(`買鞋子`);
});
//觸發(fā)監(jiān)聽“去逛街”這個事件
myEmitter.emit('去逛街'); // 輸出結(jié)果:買鞋子 買褲子 買衣服
myEmitter.emit('去逛街'); // 輸出結(jié)果:買褲子 買衣服
3.2 調(diào)用監(jiān)聽器
-
emitter.emit(eventName[, ...args])
:按照監(jiān)聽器注冊的順序所意,同步地調(diào)用每個注冊到名為 eventName 的事件的監(jiān)聽器,并傳入提供的參數(shù)催首。
3.3 移除監(jiān)聽器
-
emitter.removeListener(eventName, listener)
:從名為 eventName 的事件的監(jiān)聽器數(shù)組中移除指定的 listener扶踊。每次只會從監(jiān)聽器數(shù)組中移除一個監(jiān)聽器。 如果監(jiān)聽器被多次添加到指定 eventName 的監(jiān)聽器數(shù)組中郎任,則必須多次調(diào)用 removeListener() 才能移除所有實例秧耗。 -
emitter.removeAllListeners([eventName])
:移除全部監(jiān)聽器或指定的 eventName 事件的監(jiān)聽器。 -
emitter.off(eventName, listener)
:emitter.removeListener()
的別名。
//引入events模塊
const EventEmitter = require('events');
//創(chuàng)建一個新實例
const myEmitter = new EventEmitter();
const buyClothes = () => {
console.log(`買衣服`);
myEmitter.removeListener('去逛街', buyPants);
}
const buyPants = () => {
console.log(`買褲子`);
}
myEmitter.on('去逛街', buyClothes);
myEmitter.on('去逛街', buyPants);
// buyClothes 移除了監(jiān)聽器 buyPants,但它依然會被調(diào)用偿枕。
// 觸發(fā)時內(nèi)部的監(jiān)聽器數(shù)組為 [buyClothes, buyPants]
myEmitter.emit('去逛街'); // 買衣服 買褲子
// buyPants 現(xiàn)已被移除年局。
// 內(nèi)部的監(jiān)聽器數(shù)組為 [buyClothes]
myEmitter.emit('去逛街'); // 買衣服
4、事件
- 'newListener' 事件: EventEmitter 實例在新的監(jiān)聽器被添加到其內(nèi)部監(jiān)聽器數(shù)組之前席楚,會觸發(fā)自身的 'newListener' 事件。在 'newListener' 回調(diào)中注冊到相同 eventName的任何其他監(jiān)聽器將插入到正在添加的監(jiān)聽器之前。
const EventEmitter = require('events');
const myEmitter = new EventEmitter();
// EventEmitter 實例會在一個監(jiān)聽器被添加到其內(nèi)部監(jiān)聽器數(shù)組之前觸發(fā)自身的 'newListener' 事件缩麸;
// 只處理一次,避免無限循環(huán)。
myEmitter.once('newListener', (event, listener) => {
if (event === '去逛街') {
console.log('逛街前的準備');
myEmitter.on('去逛街', () => {
console.log('涂防曬霜');
});
}
});
myEmitter.on('去逛街', () => {
console.log('買護膚品');
});
myEmitter.emit('去逛街'); // 逛街前的準備 涂防曬霜 買護膚品
myEmitter.emit('去逛街'); // 涂防曬霜 買護膚品
注意:對'newListener' 事件的監(jiān)聽要放在普通監(jiān)聽前面杭朱。如下'newListener' 事件不起作用阅仔。
const EventEmitter = require('events');
const myEmitter = new EventEmitter();
myEmitter.on('去逛街', () => {
console.log('買護膚品');
});
myEmitter.once('newListener', (event, listener) => {
if (event === '去逛街') {
console.log('逛街前的準備');
myEmitter.on('去逛街', () => {
console.log('涂防曬霜');
});
}
});
myEmitter.emit('去逛街'); // 買護膚品
myEmitter.emit('去逛街'); // 買護膚品
- 'removeListener' 事件:'removeListener' 事件在 listener 被移除后觸發(fā)
const EventEmitter = require('events');
const myEmitter = new EventEmitter();
const buySkinProd = () => {
console.log('買護膚品');
myEmitter.removeListener('去逛街', buySkinProd);
}
myEmitter.once('removeListener', (event, listener) => {
if (event === '去逛街') {
console.log('逛街結(jié)束');
}
});
myEmitter.on('去逛街', buySkinProd);
myEmitter.emit('去逛街'); // 買護膚品 逛街結(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: "標題", 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');
/** 運行index.js結(jié)果
* listen 8000
* true
* true
* saveStart [ { title: '標題', content: '內(nèi)容' } ]
* saveEnd [ { title: '標題', content: '內(nèi)容' } ]
*/
參考文章:
https://www.jb51.net/article/124799.htm
http://www.reibang.com/p/fd1f8c998a2c
http://www.reibang.com/p/152fddf0628c