怎样轻松实现一个 EventEmitter?

怎样轻松实现一个 EventEmitter?

  之所以要特地讲解这部分知识,是因为虽然严格意义上来说,events模块属于 Node.js 服务端的知识,但是由于大多数 Node.js 核心 API构建用的是异步事件驱动架构。

在开始前请先思考几个问题:

  1. EventEmitter采用什么样的设计模式?
  2. EventEmitter常用的API是怎样实现的?

Events 基本介绍

  Node.jsevents模块对外提供了一个 EventEmitter 对象,用于对 Node.js 中的事件进行统一管理。因为 Node.js 采用了事件驱动机制,而 EventEmitter就是 Node.js 实现事件驱动的基础。在 EventEmitter的基础上,Node.js中几乎所有的模块都继承了这个类,以实现异步事件驱动架构。

  为了对此有一个大概的了解,先来看下 EventEmitter的简单使用情况,代码如下。

const events = require('events');
const eventEmitter = new events.EventEmitter();
eventEmitter.on('say',(name)=>{
    console.log('hello',name);
})
eventEmitter.emit('say','mark'); // hello mark

  以上代码中,新定义的eventEmitter是接收 events.EventEmitter模块 new之后返回的一个实例,eventEmitteremit方法,发出 say事件,通过 eventEmitteron方法监听,从而执行相应的函数。

常用的 EventEmitter 模块的 API

  除了上面的那段代码中已经使用的 onemit这两个 APIEventEmitter还提供了其他的 API方法,我通过一个表格简单整理了一下对应的方法和功能总结。

方法名方法描述
addListener(event,listener)为指定事件添加一个监听器到监听器数组的尾部
prependListener(event,listener)addListener相对,为指定事件添加一个监听器到监听器数组的头部
on(event,listener)其实就是addListener的别名
removeListener(event,listener)移除指定事件的某个监听器,监听器必须是该事件已经标注过的监听器
off(event,listener)removeListener的别名
removeAllListeners([event])移除所有事件的所有监听器,如果指定事件,则移除指定事件的所有监听器
setMaxListeners(n)默认情况下,EventEmitters中如果添加的监听器超过10个就会输出警告信息;setMaxListeners函数用于提高监听器的默认限制的数量。
listeners(event)返回指定事件的监听器数组
emit(event,[arg1],[arg2],[…])按参数的顺序执行每个监听器,如果事件有注册监听返回true,否则返回false
once(event,listener)为指定事件注册一个单次监听器,即监听器最多只会触发一次,触发后立刻解除该监听器

  除此之外,还有两个特殊的事件,不需要额外手动添加,下表所示的就是 Node.jsEventEmitter模块自带的特殊事件。

事件名事件描述
newListener该事件在添加新事件监听器的时候触发
removeListener从指定监听器数组中删除一个监听器,需要注意的是,此操作会改变处于被删监听器之后的那些监听器的索引。

addListener 和 removeListener、on 和 off 方法对比

  addListener方法的作用是为指定事件添加一个监听器,其实和 on方法实现的功能是一样的,on其实就是 addListener方法的一个别名。二者实现的作用是一样的,同时 removeListener方法的作用是为移除某个事件的监听器,同样 off也是 removeListener的别名。

  下面我们来看看addListenerremoveListener的用法,请看下面一段示例代码。

const events = require('events');
const emitter = new events.EventEmitter();

const hello1 = (name)=>{
    console.log("hello 1",name);
}

const hello2 = (name)=>{
    console.log("hello 2",name);
}

emitter.addListener('say',hello1);
emitter.addListener('say',hello2);
emitter.emit('say','mark');

emitter.removeListener('say',hello1);
emitter.emit('say','lpb');  

/* 
执行结果:  
hello 1 mark
hello 2 mark
hello 2 lpb
*/

  结合代码和注释来理解我上面的描述,是不是对于 addListenerremoveListeneronoff这两组方法的对比就一目了然了呢?

removeListener 和 removeAllListeners

  removeListener 方法是指移除一个指定事件的某一个监听器,而 removeAllListeners指的是移除某一个指定事件的全部监听器。

这里举一个removeAllListeners的代码例子,请看。

const events = require('events');
const emitter = new events.EventEmitter();

const hello1 = (name)=>{
    console.log("hello 1",name);
}

const hello2 = (name)=>{
    console.log("hello 2",name);
}

emitter.addListener('say',hello1);
emitter.addListener('say',hello2);
emitter.removeAllListeners('say');
emitter.emit('say','John');

//removeAllListeners 移除了所有关于 say 事件的监听
//因此没有任何输出

on 和 once 方法区别

  ononce 的区别是:on的方法对于某一指定事件添加的监听器可以持续不断地监听相应的事件;而 once方法添加的监听器,监听一次后,就会被消除。

const events = require("events");

const emitter = new events.EventEmitter();

const hello = (name) => {
  console.log("hello", name);
};

emitter.on("say", hello);

emitter.emit("say", "John");

emitter.emit("say", "Lily");

emitter.emit("say", "Lucy");

//会输出 hello John、hello Lily、hello Lucy,之后还要加也可以继续触发

emitter.once("see", hello);

emitter.emit("see", "Tom");

//只会输出一次 hello Tom

/* 执行结果:
hello John
hello Lily
hello Lucy
hello Tom
*/

  也就是说,on方法监听的事件,可以持续不断地被触发,而 once方法只会触发一次。

带你实现一个 EventEmitter

  EventEmitter 是在Node.jsevents模块里封装的,那么在浏览器端实现一个这样的 EventEmitter是否可以呢?其实自己封装一个能在浏览器中跑的EventEmitter,并应用在你的业务代码中还是能带来不少方便的,它可以帮你实现自定义事件的订阅和发布,从而提升业务开发的便利性。

  那么结合上面介绍的内容,我们一起来实现一个基础版本的EventEmitter,包含基础的onofemitonceallof这几个方法。

首先,请看一下 EventEmitter的初始化代码。

function EventEmitter() {

    this.__events = {}

}

EventEmitter.VERSION = '1.0.0';

  从上面的代码中可以看到,先初始化了一个内部的__events的对象,用来存放自定义事件,以及自定义事件的回调函数。

  如何实现 EventEmitteron的方法,请看下面的代码

function EventEmitter() {
  this.__events = {};
}
EventEmitter.VERSION = "1.0.0";

EventEmitter.prototype.on = function (eventName, listener) {
  // 验证参数
  if (!eventName && !listener) {
    return;
  }
  // 回调的litener是否是函数
  if (!isValidKistener(listener)) {
    throw new TypeError("listener must be a function");
  }
  const events = this.__evnets;
  const listeners = (events[eventName] = evnets[eventName] || []);
  const listenerIsWrapped = typeof listener === "object";
  // 不重复添加事件,判断是否有相同
  if (indexOf(listeners.listener) === -1) {
    listeners.push(
      listenerIsWrapped
        ? listener
        : {
            listener: listener,
            once: false,
          }
    );
  }
  return this;
};

//判断是否是合法的listener
const isValidListener = (listener) => {
  if (typeof listener === "function") {
    return true;
  } else if (listener && typeof listener === "object") {
    return isValidListener(listener.listener);
  } else {
    return false;
  }
};

// 判断新增自定义事件是否存在
const indexOf = (array, item) => {
  let result = -1;
  item = typeof item === "object" ? item.listener : item;
  for (let i = 0; i < array.length; i++) {
    if (array[i].listener === item) {
      result = i;
      break;
    }
  }
  return result;
};

  从上面的代码中可以看出,on方法的核心思路就是,当调用订阅一个自定义事件的时候,只要该事件通过校验合法之后,就把该自定义事件 pushthis.__events 这个对象中存储,等需要出发的时候,则直接从通过获取 __events 中对应事件的 listener回调函数,而后直接执行该回调方法就能实现想要的效果。

  再看看 emit方法是怎么实现触发效果的,请看下面的代码实现逻辑。

EventEmitter.prototype.emit = function (eventName,args){
    // 直接通过内部对象获取对应自定义事件的回调函数
    let listeners = this.__events[eventName];
    if (!listeners) return ;
    // 需要考虑多个listener 的情况  
    for (let i = 0;i<listeners.length;i++) {
        let listener = listeners[i];
        if (listener) {
            listener.listener.apply(this,args||[]);
            // 给listener中once为true的进行特殊处理  
            if (listener.once) {
                this.off(eventName,listener.litener);
            }
        }
    }
    return this;
};

EventEmitter.prototype.off = function(eventName,listener) {
    let listeners = this.__events[eventName];
    if (!listener) return;
    let index; 
    for (let i=0;i<listeners.length;i++) {
        if (listeners[i] && listeners[i].listener === listener) {
            index = i;
            break;
        }
    }
    // off的关键  
    if (typeof index !== 'undefined') {
        listeners.splice(index,1,null);
    }
    return this;
}

  从上面的代码中可以看出 emit的处理方式,其实就是拿到对应自定义事件进行 apply执行,在执行过程中对于一开始 once方法绑定的自定义事件进行特殊的处理,当oncetrue的时候,再触发 off方法对该自定义事件进行解绑,从而实现自定义事件一次执行的效果。

  最后,再看下 once方法和 alloff的实现。

EventEmitter.prototype.once = function (eventName,listener) {
    // 直接调用on方法,once参数传入true,待执行后进行once处理
    return this.on(eventName,{
        listener:listener,
        once:true
    })
};
EventEmitter.prototype.allOff = function (eventName,listener) {
    // 如果该eventName存在,则将其对应的listener的数组直接清空  
    if (eventName && this.__events[eventName]) {
        this.__events[eventName] = [];
    } else {
        this.__events = {}
    }
};

  从上面的代码中可以看到,once方法的本质还是调用 on方法,只不过传入的参数区分和非一次执行的情况。当再次触发 emit方法的时候,once绑定的执行一次之后再进行解绑。

  alloff 方法也很好理解了,其实就是对内部的__events对象进行清空,清空之后如果再次触发自定义事件,也就无法触发回调函数了。

总结

  现在,可以回过头思考一下在开篇提到的问题:EventEmitter采用的是什么样的设计模式?其实通过上面的学习不难发现,EventEmitter采用的正是发布-订阅模式。

  另外,观察者模式和发布-订阅模式有些类似的地方,但是在细节方面还是有一些区别的,要注意别把这两个模式搞混了。发布-订阅模式其实是观察者模式的一种变形,区别在于:发布-订阅模式在观察者模式的基础上,在目标和观察者之间增加了一个调度中心

  单就浏览器端使用场景来说,其实也有运用同样的思路解决问题的工具,在 Vue框架中不同组件之间的通讯里,有一种解决方案叫 EventBus。和 EventEmitter的思路类似,它的基本用途是将 EventBus作为组件传递数据的桥梁,所有组件共用相同的事件中心,可以向该中心注册发送事件或接收事件,所有组件都可以收到通知,使用起来非常便利,其核心其实就是发布-订阅模式的落地实现。

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值