资讯专栏INFORMATION COLUMN

EventEmitter的实现

CoreDump / 1913人阅读

摘要:实例方法的话,最核心的就是分别是添加事件,删除事件,发布事件。为了防止进程崩溃,可以在对象的事件上注册监听器,或使用模块。注意,模块已被废弃。作为最佳实践,应该始终为事件注册监听器。

前言

事件在js中非常的常见,不管是浏览器还是node,这种事件发布/订阅模式的应用都是很常见的。至于发布/订阅模式和观察者模式是否是同一种设计模式说法都有,这里不做具体的讨论。在之前的项目中也曾自己实现过一个事件模块,核心还是一个EventEmitter。下文就要结合node中的event模块分析一下,一个EventEmitter应该如何实现,有什么注意点。
源码地址
https://github.com/nodejs/nod...

基础的结构和设计

首先第一步就是一个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方法

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包裹,运行前需要对添加的监听进行移除

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的错误事件

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

如果有需要在自己的实践中也可以增加一个错误处理的机制,保证event实例的稳定性

总结

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

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/93996.html

相关文章

  • 循序渐进教你实现一个完整nodeEventEmitter模块

    摘要:本文从的的使用出发,循序渐进的实现一个完整的模块。移除指定事件的某个监听器,监听器必须是该事件已经注册过的监听器的别名移除所有事件的所有监听器,如果指定事件,则移除指定事件的所有监听器。返回指定事件的监听器数组。 node的事件模块只包含了一个类:EventEmitter。这个类在node的内置模块和第三方模块中大量使用。EventEmitter本质上是一个观察者模式的实现,这种模式可...

    sunsmell 评论0 收藏0
  • 【node不完全指西】EventEmitter (事件发布/订阅模式)解析

    摘要:从异步编程解决方案说起吧事件发布订阅模式模式流程控制库事件发布订阅模式事件监听器模式是一种广泛运用于异步编程的模式,是回调函数的事件话,又称发布订阅模式。 从node异步编程解决方案说起吧: 事件发布/订阅模式 Promise/deferred模式 流程控制库 事件发布/订阅模式 事件监听器模式是一种广泛运用于异步编程的模式,是回调函数的事件话,又称发布/订阅模式。 主要实现的几个...

    yagami 评论0 收藏0
  • 【Node事件模块Events】

    环境:Node v8.2.1; Npm v5.3.0;OS Windows10 1、 Node事件介绍 Node大多数核心 API 都采用惯用的异步事件驱动架构,其中某些类型的对象(触发器)会周期性地触发命名事件来调用函数对象(监听器)。 所有能触发事件的对象都是 EventEmitter 类的实例。 这些对象开放了一个 eventEmitter.on() 函数,允许将一个或多个函数绑定到会被对象...

    junnplus 评论0 收藏0
  • Node 之 Event 模块

    摘要:为什么把叫做集合而不能称为严格意义上的对象,来看这个集合的构造函数可以见得,是与处于同一层级的而非是继承自,所以说由实例出来的对象更加的纯净,并没有诸如等方法,更像是一个集合。 写在前面 事件的编程方式具有轻量级、松耦合、只关注事务点等优势,在浏览器端,有着自己的一套DOM事件机制,其中含包括这诸如事件冒泡,事件捕获等;然而Node的事件机制没有事件冒泡等,其原理就是设计模式中的观察者...

    mrli2016 评论0 收藏0
  • 从观察者模式到手写EventEmitter源码

    摘要:观察者模式观察者模式广泛的应用于语言中,浏览器事件如鼠标单击,键盘事件都是该模式的例子。可以看到,这就是观察者模式的订阅方法实现。小结通过创建可观察的对象,当发生一个感兴趣的事件时可将该事件通告给所有观察者,从而形成松散的耦合。 观察者模式 观察者模式(observer)广泛的应用于javascript语言中,浏览器事件(如鼠标单击click,键盘事件keyDown)都是该模式的例子。...

    cocopeak 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<