Facebook Emitter Source Code

EventEmitter 是一个事件订阅、分发库, facebook 出品,被应用在 flux 等库中,也可以独立使用。github 地址 https://github.com/facebook/emitter

源码比较简单,只是有两点可以注意一下:

1、生成 subscription 的 id。

1
2
3
4
5
6
7
8
9
10
11
//EventSubscriptionVendor
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
//EventSubscriptionVendor
removeSubscription(subscription: Object) {
const eventType = subscription.eventType;
const key = subscription.key;

const subscriptionsForType = this._subscriptionsForType[eventType];
if (subscriptionsForType) {
// delete subscriptionsForType[key];
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');

// output
// click trigger 1
// click trigger 2

原因就是 removeAllListeners 的实现使用的是 delete,清除了键,但是没有清空键对应的值集合:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
//BaseEventEmitter
removeAllListeners(eventType: ?TEvent): void {
this._subscriber.removeAllSubscriptions(eventType);
}

//EventSubscriptionVendor
removeAllSubscriptions(eventType: ?String) {
if (eventType === undefined) {
this._subscriptionsForType = {};
} else {
delete this._subscriptionsForType[eventType];
}
}

再看 emit 的实现:

1
2
3
4
5
6
7
8
//BaseEventEmitter
emit(eventType: TEvent): void {
var subscriptions = this._subscriber.getSubscriptionsForType(eventType);
if (subscriptions) {
//other
}
}

由于 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
//BaseEventEmitter
removeCurrentListener(): void {
this._subscriber.removeSubscription(this._currentSubscription);
}

//EventSubscriptionVendor
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
//EventSubscriptionVendor
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,不过对数据结构改动较大,而且依旧需要遍历。