EventEmitter 是一个事件订阅、分发库, facebook 出品,被应用在 flux 等库中,也可以独立使用。github 地址 https://github.com/facebook/emitter。
源码比较简单,只是有两点可以注意一下:
1、生成 subscription 的 id。
1 2 3 4 5 6 7 8 9 10 11
| addSubscription( eventType, subscription) { if (!this._subscriptionsForType[eventType]) { this._subscriptionsForType[eventType] = []; } const key = this._subscriptionsForType[eventType].length; this._subscriptionsForType[eventType].push(subscription); subscription.eventType = eventType; subscription.key = key; return subscription; }
|
这里使用数组长度作为 subscription 的 id,就意味着后续删除 subscription 的时候,是不能修改数组长度的,也就决定了只能讲数组元素置空,不能移除。如果我们稍稍微做个变更:
1 2 3 4 5 6 7 8 9 10 11 12
| removeSubscription(subscription: Object) { const eventType = subscription.eventType; const key = subscription.key;
const subscriptionsForType = this._subscriptionsForType[eventType]; if (subscriptionsForType) { subscriptionsForType.splice(key, 1); } }
|
使用方法如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| const EventEmitter = require('./lib/EventEmitter');
const emitter = new EventEmitter(); emitter.addListener('click', () => { console.log('click trigger 1'); emitter.addListener('click', () => { console.log('click trigger 2'); }); emitter.removeCurrentListener(); emitter.removeCurrentListener(); });
emitter.emit('click'); emitter.emit('click'); emitter.emit('click');
|
预期应该是
click trigger 1
click trigger 3
click trigger 2
但实际上只能得到:
click trigger 1
原因就是数组长度作为 id 冲突了
2、delete 的使用
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| const EventEmitter = require('./lib/EventEmitter');
const emitter = new EventEmitter(); emitter.addListener('click', () => { console.log('click trigger 1'); emitter.removeAllListeners('click'); }); emitter.addListener('click', () => { console.log('click trigger 2'); });
emitter.emit('click'); emitter.emit('click');
|
原因就是 removeAllListeners 的实现使用的是 delete,清除了键,但是没有清空键对应的值集合:
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| removeAllListeners(eventType: ?TEvent): void { this._subscriber.removeAllSubscriptions(eventType); }
removeAllSubscriptions(eventType: ?String) { if (eventType === undefined) { this._subscriptionsForType = {}; } else { delete this._subscriptionsForType[eventType]; } }
|
再看 emit 的实现:
1 2 3 4 5 6 7 8
| emit(eventType: TEvent): void { var subscriptions = this._subscriber.getSubscriptionsForType(eventType); if (subscriptions) { } }
|
由于 emit 的一开始就获取了 eventType 所对对应的 subscriptions,因此就算在当前 event cycle 中主动调用 removeAllListeners 清除掉其余的订阅,在当前 event cycle 中也无法生效。
与此对比的就是 removeCurrentListener :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| removeCurrentListener(): void { this._subscriber.removeSubscription(this._currentSubscription); }
removeSubscription(subscription: Object) { const eventType = subscription.eventType; const key = subscription.key;
const subscriptionsForType = this._subscriptionsForType[eventType]; if (subscriptionsForType) { delete subscriptionsForType[key]; } }
|
removeCurrentListener 更进一步删除掉了 subscriptionsForType 中对应的 key,所以一次 emit 创建的 event cycle 里面哪怕保存了 subscriptions,被移除掉的 subscription 也不会再次被触发了。所以如果想实现彻底的 removeAllListeners,还是得清理掉实际的 subscription 。处理方式其实有好些,比如:
(1)清理掉 subscriptions 的值集合。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| removeAllSubscriptions(eventType: ?String) { if (eventType === undefined) { Object.values(this._subscriptionsForType).forEach(subscriptionsForType => { subscriptionsForType.forEach(subscription => { this.removeSubscription(subscription); }); }); this._subscriptionsForType = {}; } else { this._subscriptionsForType[eventType].forEach(subscription => { this.removeSubscription(subscription); }); delete this._subscriptionsForType[eventType]; } }
|
(2)给 subscription (或者数据结构层次)设置一个取消标志,如果 subscription 被取消了,则不执行 listener,不过对数据结构改动较大,而且依旧需要遍历。