Node 的 Events 模块只定义了一个类,就是 EventEmitter,这个类在很多Node 本身以及第三方模块中大量使用,通常是用作基类被继承。
本内容,我们来把EventEmitter的on,once,off,emit方法进行简单的手写实现。
Events模块基本使用
events模块大家使用思路基本都一致,都是继承出来使用,但是实现方式多种多样,这里默认大家都知道events模块的使用方式,不再赘述,贴一种基本使用方法吧,让不清楚使用方式的朋友有一个大概了解。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| const EventEmitter = require('events')
function MyFun() { }
MyFun.prototype = Object.create(EventEmitter.prototype)
let myFun = new MyFun()
myFun.on('eventName', () => { }) myFun.on('eventName', () => { })
myFun.emit('eventName')
|
Events手写实现
咱们会对基础功能简单的手写实现,就里就不用Function写法直接采用ES6的Class语法来写了。
on与emit方法实现
咱们通过on方法订阅事件,通过emit方法来发布事件,按照这个思路,不难写出以下代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
| class EventEmitter { constructor() { this._events = {} }
on(eventName, callback) { if (this._events[eventName]) { this._events[eventName].push(callback) } else { this._events[eventName] = [callback] } }
emit(eventName, ...args) { this._events[eventName].forEach(fn => { fn(...args) }) } }
|
看起来好像很顺利,但是不要忽略的一个问题,我们继承的时候只是继承了原型上的方法,在实例上还可能是没有this._events的,所以我们需要做一下判断,如果没有我们要生成一个:
1 2 3 4 5 6 7 8 9 10
| on(eventName, callback) { if (!this._events) { this._events = {} } if (this._events[eventName]) { this._events[eventName].push(callback) } else { this._events[eventName] = [callback] } }
|
off和once方法实现
通过Array.prototype.filter方法把取消订阅的方法给过滤掉。
1 2 3 4 5 6 7 8 9 10 11 12 13
|
off(eventName, callback) { if(this._events && this._events[eventName]){ this._events[eventName] = this._events[eventName].filter(fn => { return fn !== callback }) } }
|
once方法注意的是只会触发一次,也就是我们要在回调函数执行后取消订阅,所以我们可以封装为一个高阶函数,将这个高阶函数作为订阅的回调函数,在这里执行真正的回调并取消订阅
这里用箭头函数是为了保证this指向没问题
1 2 3 4 5 6 7 8 9 10 11 12
|
once(eventName, callback) { const one = () => { callback() this.off(eventName, one) } this.on(eventName, one) }
|
上边的代码看起来好像也没什么问题了,但是我们要想一下,如果我们通过once方法订阅了事件,但是在发布事件之前我们就通过off方法取消订阅,这样会不会有问题呢?示例如下:
1 2 3
| const fn = eventEmitter.once('eventName', fn) eventEmitter.off('eventName', fn)
|
回看我们的once方法,我们对传入的callback进行了封装,最后添加到订阅列表的是一个名为one的方法,而此时我们传入fn方法取消订阅,肯定不能成功的把这个回调函数给过滤掉,所以我们需要在once方法中把传入的回调也收集起来。
once方法修改如下:
1 2 3 4 5 6 7 8 9
| once(eventName, callback) { const one = () => { callback() this.off(eventName, one) } + one.l = callback; this.on(eventName, one) }
|
相应的,我们在off方法中也要添加判断:
1 2 3 4 5 6 7 8
| off(eventName, callback) { if (this._events && this._events[eventName]) { this._events[eventName] = this._events[eventName].filter(fn => { + return fn !== callback && fn.l !== callback }) } }
|
完整手写代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59
| class EventEmitter { constructor() { this._events = {} }
on(eventName, callback) { if (!this._events) { this._events = {} } if (this._events[eventName]) { this._events[eventName].push(callback) } else { this._events[eventName] = [callback] } }
emit(eventName, ...args) { this._events[eventName].forEach(fn => { fn(...args) }) }
off(eventName, callback) { if (this._events && this._events[eventName]) { this._events[eventName] = this._events[eventName].filter(fn => { return fn !== callback && fn.l !== callback }) } }
once(eventName, callback) { const one = () => { callback() this.off(eventName, one) } one.l = callback; this.on(eventName, one) } }
|