发布订阅模式---模拟Node中的EventEmitter

1,948 阅读3分钟

发布 — 订阅模式

发布 — 订阅模式又叫观察者模式,它定义对象间的一种一对多的依赖关系,当一个对象的状 态发生改变时,所有依赖于它的对象都将得到通知。

在 JavaScript开发中,我们一般用事件模型来替代传统的发布 — 订阅模式。

实现的关键要素

  • 发布者有一个订阅者缓存队列
  • 发布者有增加和删除订阅者的方法
  • 发布者状态改变,需要notify方法通知队列中的所有订阅者
  • js中采用事件回调的方式来更新订阅者,因此订阅者不再需要update方法

下面来模拟下EventEmitter的初步实现

class EventEmitter {
    constructor() {
        this._events = {};//用对象的方式来缓存订阅者队列(事件名称:回调)
    }

    on(eventName, listener) {
        if(typeof listener !== 'function') { return; }
        
        if(!this._events) {//如果只被继承了prototype,需要在继承的对象上添加_events属性
            this._events = Object.create(null);
        }

        if(!this._events[eventName]) {//事件队列不存在
            this._events[eventName] = [];
        }

        this._events[eventName].push(listener);//添加观察者
    }

    addListener(eventName, listener) {
        this.on(eventName, listener);
    }

    removeListener(eventName, listener) {
        if(!this._events[eventName]) { return; }

        this._events[eventName] = this._events[eventName].forEach(item => {
            return item !== listener;
        });
    }

    emmit(eventName, ...args) {//状态改变
        if(!this._events[eventName]) { return; }

        this._events[eventName].forEach(callback => {//通知所有的订阅者,发起回调
            callback.apply(this, args);
        });
    }
}

EventEmitter中的once方法可以做到绑定的事件只调用一次,之后不会再被调用,他的实现方式实在怎么样的?正常情况应该是在回调函数被调用一次之后移除这个回调。可以考虑在回调函数上加上once属性,在发起回调的时候判断once是否为真,来确定是否移除这个回调。这样可以达到目的,但是在发起回调时,需要每一次都判断,给通知方法增加了额外的负担,来考虑一个更聪明的实现方式。

wrap函数

once(eventName, listener) {
    function wrap(args) {
        listener.apply(this, args);
        this.removeListener(eventName, wrap);
    }

    wrap.cb = listener;//将回调存储起来用于删除时对比

    this.on(eventName, wrap);
}

将回调函数包裹起来,在包裹函数内部移除原回调函数,然后将wrap函数添加进观察者队列。同时要将原回调函数存进wrap中,用在在移除原回调时判断。

修改移除观察者方法

removeListener(eventName, listener) {
    if(!this._events[eventName]) { return; }

    this._events[eventName] = this._events[eventName].forEach(item => {
        return item !== listener && item.cb !== listener;
    });
}

newListener事件

比较有趣的是EventEmitter同时提供了newListener事件,每次添加观察者(即使是第二次添加newListener)时都会触发这个事件,在on方法中需要添加如下代码:

this.emmit('newListener', eventName, listener);//触发newListener事件回调

defaultMaxListeners

这个静态属性限制了一种事件可以添加的最大回调数量,同时还有配套的setMaxListeners和getMaxListeners方法来设置和获取每个事件可以添加的最大回调数量

setMaxListeners(n) {
    this.maxListeners = n;
}

getMaxListeners() {
    return this.maxListeners ? this.maxListeners : EventEmitter.defaultMaxListeners;
}

on方法添加判断;

if(this._events[eventName].length > this.getMaxListeners()){
    console.warn('超过最大数量,请修改maxListeners')
}

完整代码

class EventEmitter {
    constructor() {
        this._events = {};//用对象的方式来缓存订阅者队列(事件名称:回调)
    }

    setMaxListeners(n) {
        this.maxListeners = n;
    }

    getMaxListeners() {
        return this.maxListeners ? this.maxListeners : EventEmitter.defaultMaxListeners;
    }

    on(eventName, listener) {
        if(typeof listener !== 'function') { return; }

        if(!this._events) {//如果只被继承了prototype,需要在继承的对象上添加_events属性
            this._events = Object.create(null);
        }

        this.emmit('newListener', eventName, listener);//触发newListener事件回调

        if(!this._events[eventName]) {//事件队列不存在
            this._events[eventName] = [];
        }

        this._events[eventName].push(listener);//添加观察者

        if(this._events[eventName].length > this.getMaxListeners()){
            console.warn('超过最大数量,请修改maxListeners')
        }
    }

    once(eventName, listener) {
        function wrap(args) {
            listener.apply(this, args);
            this.removeListener(eventName, wrap);
        }

        wrap.cb = listener;//将回调存储起来用于删除时对比

        this.on(eventName, wrap);
    }

    addListener(eventName, listener) {
        this.on(eventName, listener);
    }

    removeListener(eventName, listener) {
        if(!this._events[eventName]) { return; }

        this._events[eventName] = this._events[eventName].forEach(item => {
            return item !== listener && item.cb !== listener;
        });
    }

    emmit(eventName, ...args) {//状态改变
        if(!this._events[eventName]) { return; }

        this._events[eventName].forEach(callback => {//通知所有的订阅者,发起回调
            callback.apply(this, args);
        });
    } 
}