构造函数很简单,就一行代码,主要逻辑都在EventEmitter.init里面:
EventEmitter.init里面也是做了一些初始化的工作,this._events跟我们自己写的this.events功能是一样的,用来存储订阅的事件。核心代码我在图上用箭头标出来了。这里需要注意一点,如果一个类型的事件只有一个订阅,this._events就直接是那个函数了,而不是一个数组,在源码里面我们会多次看到对这个进行判断,这样写是为了提高性能。
订阅事件代码传送门:
EventEmitter订阅事件的API是on和addListener,从源码中我们可以看出这两个方法是完全一样的:
这两个方法都是调用了_addListener,这个方法对参数进行了判断和错误处理,核心代码仍然是往this._events里面添加事件:
发布事件代码传送门:
EventEmitter发布事件的API是emit,这个API里面会对"error"类型的事件进行特殊处理,也就是抛出错误:
如果不是错误类型的事件,就把订阅的回调事件拿出来执行:
取消订阅代码传送门:
EventEmitter里面取消订阅的API是removeListener和off,这两个是完全一样的。EventEmitter的取消订阅API不仅仅会删除对应的订阅,在删除后还会emit一个removeListener事件来通知外界。这里也会对this._events里面对应的type进行判断,如果只有一个,也就是说这个type的类型是function,会直接删除这个键,如果有多个订阅,就会找出这个订阅,然后删掉他。如果所有订阅都删完了,就直接将this._events置空:
观察者模式这里再提一个很相似的设计模式:观察者模式,有些文章认为他和发布订阅模式是一样的,有些认为他们是有区别的。笔者认为他更像一个低配版的发布订阅模式,我们来实现一个看看:
class Subject { constructor() { // 一个数组存放所有的订阅者 // 每个消息对应一个数组,数组结构如下 // [ // { // observer: obj, // action: () => {} // } // ] this.observers = []; } addObserver(observer, action) { // 将观察者和回调放入数组 this.observers.push({observer, action}); } notify(...args) { // 执行每个观察者的回调 this.observers.forEach(item => { const {observer, action} = item; action.call(observer, ...args); }) } } const subject = new Subject(); // 添加一个观察者 subject.addObserver({name: 'John'}, function(msg){ console.log(this.name, 'got message: ', msg); }) // 再添加一个观察者 subject.addObserver({name: 'Joe'}, function(msg) { console.log(this.name, 'got message: ', msg); }) // 通知所有观察者 subject.notify('tomorrow is Sunday');上述代码的输出是:
通过这个输出可以看出一旦调了通知的方法notify,所有观察者都会收到通知,而且会收到同样的信息。而发布订阅模式还可以自定义需要接受的通知,所以说观察者模式是低配版的发布订阅模式。
总结本文讲解了发布订阅模式的原理,并自己实现了一个简单的发布订阅模式。在了解了原理后,还去读了Node.js的EventEmitter模块的源码,进一步学习了生产环境的发布订阅模式的写法。总结下来发布订阅模式有以下特点:
解决了“回调地狱”
将多个模块进行了解耦,自己执行时,不需要知道另一个模块的存在,只需要关心发布出来的事件就行