EventEmitter的实现

前言

事件在js中很是的常见,无论是浏览器仍是node,这种事件发布/订阅模式的应用都是很常见的。至于发布/订阅模式和观察者模式是不是同一种设计模式说法都有,这里不作具体的讨论。在以前的项目中也曾本身实现过一个事件模块,核心仍是一个EventEmitter。下文就要结合node中的event模块分析一下,一个EventEmitter应该如何实现,有什么注意点。node

基础的结构和设计

首先第一步就是一个EventEmitter的类,而后考虑一下这个类的实例属性和实例方法。
实例属性的话最基础的就是一个eventMap,能够是一个空对象,固然也能够这样建立Object.create(null)。若是须要还能够增长maxListener之类的属性。
实例方法的话,最核心的就是add delete emit分别是添加事件,删除事件,发布事件。固然实际实现的时候,例如count,has,once(一次性添加),preAdd(添加在事件队列最前面),这些方法则是能够根据实际需求去添加。设计模式

具体实现及注意点

如下代码均为简化的伪代码浏览器

add方法

EventEmitter.prototype.add = function(type, fn) {
    if (!isFunction(fn)) return;//判断是否在监听中添加的是合法的函数
    //判断type是否添加过,添加过一个仍是多个函数
    if (this.event[type]) {
        if (isArray(this.event[type])){
            //若是想要实现preadd将push改成unshift便可
            this.event[type].push(fn);
        } else {
            //若是想要实现preadd改变顺序
            this.event[type] = [this.event[type], fn];
        }
    } else {
        this.event[type] = fn;
    }
}

once方法

参考一下node的once方法app

function onceWrapper(...args) {
  if (!this.fired) {
    this.target.removeListener(this.type, this.wrapFn);
    this.fired = true;
    Reflect.apply(this.listener, this.target, args);
  }
}

function _onceWrap(target, type, listener) {
  var state = { fired: false, wrapFn: undefined, target, type, listener };
  var wrapped = onceWrapper.bind(state);
  wrapped.listener = listener;
  state.wrapFn = wrapped;
  return wrapped;
}

EventEmitter.prototype.once = function once(type, listener) {
  this.on(type, _onceWrap(this, type, listener));
  return this;
};

函数用onceWrap包裹,运行前须要对添加的监听进行移除dom

delete

很简单理清楚几种边界状况就能够了函数

EventEmitter.prototype.delete = function(type, fn) {
    //直接删除整类监听
    if(fn === undefined){
        this.events[type] && delete this.events[type];
    }else{
        //判断fn合法性就省了
        if(this.events[type]) {
            if (this.events[type] === fn) {
                delete this.events[type];
            } else {
                for (var i in this.events[type]) {
                    if(this.events[type][i] === fn){
                        if (i === 0) {
                            this.events[type].shift();
                        } else {
                            this.events[type].splice(i,1);
                        }
                    }
                }
                if(this.events[type].length === 1) this.events[type] = this.events[type][0];
            }
        }
    }
    
}

emit

EventEmitter.prototype.emit = function(type) {
    //获取参数
    var args = [].slice.call(arguments, 1);
    var handler = events[type];

    if (handler === undefined) return false;

    if (typeof handler === 'function') {
        handle.apply(this, args);
    } else {
        var len = handler.length;
        const listeners = arrayClone(handler, len);
        for (var i = 0; i < len; ++i)
             handle[i].apply(this, args);
   }
}

发布事件有两个注意点,一个是注意参数的保留,另外一个则是上下文,这里上下文是直接取了event实例的上下文,也能够考虑一下手动传入上下文的形式,或者说fn在定义的时候直接写成箭头函数,也能够避免上下文成为eventEmit实例的上下文。学习

错误处理

这里提一下node的event的错误事件this

当 EventEmitter 实例中发生错误时,会触发一个 'error' 事件。 这在 Node.js 中是特殊状况。
若是 EventEmitter 没有为 'error' 事件注册至少一个监听器,则当 'error' 事件触发时,会抛出错误、打印堆栈跟踪、且退出 Node.js 进程。
为了防止 Node.js 进程崩溃,能够在 process 对象的 uncaughtException 事件上注册监听器,或使用 domain 模块。 (注意,domain 模块已被废弃。)
做为最佳实践,应该始终为 'error' 事件注册监听器。

若是有须要在本身的实践中也能够增长一个错误处理的机制,保证event实例的稳定性prototype

总结

一个事件订阅发布类其实不难实现,而在node中有不少厉害的类都是继承的事件类,而以后我会接着对node文件系统进行学习设计

相关文章
相关标签/搜索