Facebook Emitter Source Code

2019-06-06

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

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

1、生成 subscription 的 id。

    //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 的时候,是不能修改数组长度的,也就决定了只能讲数组元素置空,不能移除。如果我们稍稍微做个变更:

    //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);
        }
    }

使用方法如下:

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 的使用

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,清除了键,但是没有清空键对应的值集合:

    //BaseEventEmitter
    removeAllListeners(eventType: ?TEvent): void {
        this._subscriber.removeAllSubscriptions(eventType);
    }

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

再看 emit 的实现:

    //BaseEventEmitter
    emit(eventType: TEvent): void {
        var subscriptions = this._subscriber.getSubscriptionsForType(eventType);
        if (subscriptions) {
            //other
        }
    }

由于 emit 的一开始就获取了 eventType 所对对应的 subscriptions,因此就算在当前 event cycle 中主动调用 removeAllListeners 清除掉其余的订阅,在当前 event cycle 中也无法生效。

与此对比的就是 removeCurrentListener :

    //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 的值集合。

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

如有问题,请咨询QQ群 723245925


文档信息 by XiaoPingYuan

版权声明:自由转载-非商用-非衍生-保持署名。发表日期:2019-06-06 by XiaoPingYuan(https://xesam.github.io/)