Node中的事件发布与订阅
事件的发布和订阅系统(event)是NodeJS中的一个极为重要的系统,但是这个系统对于很多刚入坑的小伙伴来说可能难以理解,
这篇博客会为刚入坑的小伙伴们简单介绍一下事件(event),并且一步一步的写一个“山寨版”的事件系统。
1. 什么是事件系统
事件系统其实就是NodeJS中的一个模块(Events),这个模块使得NodeJS具有对事件的监听和触发的动能。
比如说,我们可以制定一个机制,一旦一个文件读取完毕,我们会做什么事情,比如将文件内容打印出来或者进行修改。
事件系统是NodeJS中的一个极其重要的系统,因为Javascript语言原本是前端的脚本语言,对于这种事件机制支持
比较弱,而这种机制又是后端语言必备的,所以NodeJS实现了一套很完善的事件系统。许多初识Node的同学可能
会觉得事件机制比较难理解,但其实真没有那么难。
2. 如何使用事件系统
在NodeJS中有个模块叫做Events,它的作用就是让我们很容易的去使用事件。
- 首先,要导入events模块。由于这个模块是一个构造函数,所以我们需要new一个event实例。 - 1 
 2
 3- var Events = require('events'); 
 var myEvent = new Events();
- 在这个实例的原型上有很多方法,这里只说两个最简单的,其他的可以在NodeJS的官方API上面看到。 
 on方法负责定义事件并且绑定到一个监听者(listener)上,就像这样- 1 
 2
 3- myEvent.on('hello', function(){ 
 console.log('hello back');
 });- 这里,我们定义了一个名为hello的事件,而监听者一旦发现这个事件被触发了,就会执行function里面的内容。 
- 一个事件上可以绑定多个监听者,所以我们再为这个名为hello的事件绑定一个吧 - 1 
 2
 3- myEvent.on('hello', function(){ 
 console.log('Ignored')
 });
- 事件绑定好了,我们来触发一下这个事件,看看我们绑定的监听者会不会生效,然而触发事件我们 
 只需要调用它的emit方法就可以了,就像这样- 1 - myEvent.emit('hello'); - 执行的结果如下 - 1 
 2- hello back 
 Ignored
3.自己写一个山寨版的事件系统
在学习编程中有一个很好的方法,就是 “山寨”,或者发明轮子,其实就是通过实现一个已经有的功能来加深对这个
功能的理解,那么我们也来“山寨”一个最简单的事件系统吧,来看看事件到底是个什么东西。
- 首先,我们知道了NodeJS的事件模块是个构造函数,所以我们也来写一个构造函数。这里我们给一个空对象,这个对象会 
 用来存放各类的事件(event),比如之前我们用过的hello事件。- 1 
 2
 3- function Events(){ 
 this.events = {};
 }
- 刚才我们一共使用了两个方法,on和emit对吧,这里我们也要在原型上扩展这两个方法,以达到让“山寨版”可以像“正版”一样工作。 
 首先on可以接受两个参数,一个是事件的名字(hello),另一个是事件触发时需要执行的方法,而emit只需要接受一个事件名就可以了- 1 
 2
 3
 4
 5
 6
 7
 8- Events.prototype.on = function(type, responseFunction){ 
 
 };
 Events.prototype.emit = function(type){
 
 };
- 想想当on方法执行时发生了什么,它把一个方法绑定到一个事件上了,所以大概是这样的 - 1 
 2
 3- Events.prototype.on = function(type, responseFunction){ 
 this.events[type] = responseFunction;
 };- 这里说明一下,在 - 1 
 2
 3- function Events(){ 
 this.events = {};
 }- 中,this.events这个object大概会是一个这样的结构,每个受到绑定的事件都会对应相应的方法,就像这样 - 1 
 2
 3
 4
 5- { 
 hello: function(){...},
 fileLoaded: function(){...},
 ...
 }- 比如,当hello被触发时,里面的function就会执行。 
- 同理,我们再来想想当调用了emit方法时发生了什么,对,事件对应的方法被执行了,所以大概会是这样, - 1 
 2
 3- Events.prototype.emit = function(type){ 
 this.events[type]();
 };- 注意,这里说明一下,我们调用某个object上的一个方法或者访问这个object上的某个属性时,可以通过两种方法: 
 一是通过打点的方法,比如- obj.xxx(属性),- obj.xxx()(方法);
 二是通过方括号的方法,比如- obj['xxx'](属性),- obj['xxx']()(方法)。
 但是这里我们只能用后者,因为我们不是要调用this.events的type属性而是要调用通过type传进来的那个属性,
 换句话说,type是个变量,它的值才是我们真正要调用的,比如events.hello。
- 好了,这样就差不多了。哦,对了!一会别的文件要引用这个模块,所以我们要导出一下, - 1 - module.exports = Events; 
- 来执行一下吧,把刚才的 - require('events')换成我们的“山寨版”吧- require('./modules/myEvents'),
 走起!!!
 输出:- Ignored
 Yeah!!!
 等等,好像哪里不对啊,刚才明明绑定了两个监听者现在怎么只输出了一个啊?这什么情况。。。
3.山寨版改造
- 通过分析刚才我们写的代码,很明显,每次我们调用on方法时,会给event实例重新赋值,换句话说,之后的会覆盖之前的, 
 所以要让我们的“山寨版具有和”正版一样对一个事件多次绑定的功能,我们需要用一个集合来存放同一事件绑定的所有监听者,- 1 
 2
 3
 4
 5
 6
 7
 8- Events.prototype.on = function(type, responseFunction){ 
 if(this.events[type]){
 this.events[type].push(responseFunction);
 }else{
 this.events[type] = [];
 this.events[type].push(responseFunction);
 }
 };- 这里的逻辑是,如果一个事件(event)之前已经定义过了,那我们就直接在这个事件所对应的监听者数组里push新绑定的方法, 
 如果没有定义过,那么就定义一个空监听者数组,然后把对应的方法push进去。说白了,第一次我们绑定hello这个事件的时候,
 实例的events上并没有这个叫hello的属性,那么我们定义一个数组,然后把hello对应的方法push进去,第二次我们发现已经
 有hello了,就在相应的数组里push一个新的方法。hello这个事件大概是这样的
 第一次:- 1 
 2
 3
 4
 5
 6- //on执行前 
 events:{}
 //on执行后
 events:{
 hello: [function(){...}]
 }- 第二次 - 1 
 2
 3
 4
 5
 6
 7
 8- //on执行前 
 events:{
 hello: [function(){...}]
 }
 //on执行后
 events:{
 hello: [function(){...}, function(){...}]
 }- 我们可以对这段代码进行一下简化,说白了就是想少写点字 - 1 
 2
 3
 4- Events.prototype.on = function(type, responseFunction){ 
 this.events[type] = this.events[type] || [];
 this.events[type].push(responseFunction);
 };- OK,on这个方法就大概写完了。 
- 既然events.hello已经是一个包含了多个function的数组,所以我们不能像之前那样 - this.events[type]();简单粗暴的直接调用了,我们需要执行数组里的每一个function,也就是
 对这个数组进行一下遍历,这里有很多种方法,我们用forEach,因为效率比较高。- 1 
 2
 3
 4
 5- Events.prototype.emit = function(type){ 
 this.events[type].forEach(function(responseFunc){
 responseFunc();
 })
 };- 好了,到这里这个“山寨版”也就差不多完成了,我们来跑跑吧: - 1 
 2- hello back 
 Ignored- 非常好!这样我们的“山寨版”也能绑定多个监听者了。 
完整代码如下
./modules/myEvents.js1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16function Events(){
    this.events = {};
}
Events.prototype.on = function(type, responseFunction){
    this.events[type] = this.events[type] || [];
    this.events[type].push(responseFunction);
};
Events.prototype.emit = function(type){
    this.events[type].forEach(function(responseFunc){
        responseFunc();
    })
};
module.exports = Events;
./app.js1
2
3
4
5
6
7
8
9
10
11
12
13
14var Events = require('./modules/myEvents');
var myEvent = new Events();
myEvent.on('hello', function(){
    console.log('hello back');
});
myEvent.on('hello', function(){
    console.log('Ignored')
});
myEvent.emit('hello');
4.总结
我们写了一个“山寨版”的事件绑定和发布系统,但是山寨毕竟是山寨,我们看看NodeJS源码中的Events模块。。。
我滴神呐。。。同样是程序猿,代码的差距咋就这么大捏。。。
那是当然,人家的代码有很多我们没有的方法,而且人家有很多异常处理等等。但是如果你仔细看正版的on和emit方法
会发现其实他们实现的方法和我们的差不太多,那是当然,我是看过源码才。。。我是说。。。我是受到了源码的启发
才写的这篇博客。