本身动手,实现一个带历史消息的EventEmitter

前言

众所周知,EventEmitter是咱们前端开发常常须要使用的一个类(node端自带,浏览器端gaythub上有许多现成的npm包)。它可以帮助咱们订阅、发送自定义事件,在跨组件通讯中常常用到。但使用这个类的时候有个问题:咱们必须在发射某事件前订阅它,后来订阅者是没法接收前面的事件的。前端

项目中每每一些千奇百怪的bug在定位后发现,就是由于错误这个时机而形成的:( 所以咱们须要额外当心这个订阅时机。node

为了解决这个说大不大,说小不小的问题,今天咱们就来造个轮子,实现一个带历史消息的EventEmiittertypescript

实现一个常规的EventEmitter

首先咱们先实现一个普通的EventEmitter,它应该包含onoffemit三个基本的API,此外,为了更好地说明一些参数格式,我这里使用了typescript,不了解ts的童鞋也不要紧,它的格式很是简单易懂~npm

class EventEmitter {
  private eventStack: Array<{
    name: string;
    callback: Function;
  }>;

  constructor() {
    this.eventStack = [];
  }

  // 订阅事件
  on(name: string, callback: Function) {
    this.eventStack.push({ name, callback });
  }

  // 取消订阅
  off(name?: string, callback?: Function) {
    if (!name) {
      this.eventStack = [];
    } else if (!callback) {
      this.eventStack.filter(item => item.name !== name);
    } else {
      this.eventStack.filter(
        item => item.name !== name && item.callback !== callback,
      );
    }
  }

  // 发射事件
  emit(name: string, data?: any) {
    data = data || null;
    this.eventStack.forEach(item => {
      item.name === name && item.callback(data);
    });
  }
}
复制代码

首先咱们定义了一个EventEmitter类,同时也定义了onoffemit三个基本API,同时定义了一个内部属性eventStack —— 一个存放订阅对象的数组(订阅对象是一个由事件名name和回调函数callback组成的对象),它是实现该类的核心。数组

on

当调用者执行on方法时,其实就是将事件名和回调函数组成订阅者对象,而后推入eventStack浏览器

off

因为off支持取消订阅某一事件的某一回调、取消订阅某一事件的全部回调,以及取消全部事件的订阅三种状况,所以这里根据传参的有无,利用filter删除了eventStack的部分订阅对象函数

emit

这个方法也很简单,利用forEach找到一致事件名的订阅对象,执行其回调便可“发射出去”ui

增长历史版本功能

接下来是重点内容,为了让后来订阅者能接收先前的消息,咱们须要增长一个叫“历史消息”的东西,它应该存放EventEmitter从建立之初到迄今的全部事件和数据,所以咱们新增了一个内部属性:this

// 历史消息,name:事件名,data:消息数据
private historyData: Array<{
    name: string;
    data: any;
  }>;
复制代码

而后咱们在emit方法里作一些更改,在发射当前消息之余,咱们也应该将其保存到historyData中:spa

emit(name: string, data?: any) {
  data = data || null;
  // 保存事件历史
  this.historyData.push({ name, data });
  // 发射事件
  this.eventStack.forEach(item => {
    item.name === name && item.callback(data);
  });
}
复制代码

其次,在on方法中咱们也须要进行相应修改:在订阅的一刹那查阅下历史消息,康康是否存在同类型的事件,若是有,则直接执行回调,此外,为了提升on方法的扩展性,咱们增长了第三个参数,用于决定是否开启“历史消息推送”功能:

on(name: string, callback: Function, isNeedHistoryData?: boolean) {
  this.eventStack.push({ name, callback });
  isNeedHistoryData &&
    this.historyData.forEach(item => {
      item.name === name && callback(item.data);
    });
}
复制代码

finally,咱们的最终代码长这样:

export default class Event {
  private historyData: Array<{
    name: string;
    data: any;
  }>;
  private eventStack: Array<{
    name: string;
    callback: Function;
  }>;

  constructor() {
    this.historyData = [];
    this.eventStack = [];
  }

  on(name: string, callback: Function, isNeedHistoryData?: boolean) {
    this.eventStack.push({ name, callback });
    isNeedHistoryData &&
      this.historyData.forEach(item => {
        item.name === name && callback(item.data);
      });
  }

  off(name?: string, callback?: Function) {
    if (!name) {
      this.eventStack = [];
    } else if (!callback) {
      this.eventStack.filter(item => item.name !== name);
    } else {
      this.eventStack.filter(
        item => item.name !== name && item.callback !== callback,
      );
    }
  }

  emit(name: string, data?: any) {
    data = data || null;
    // 保存事件历史
    this.historyData.push({ name, data });
    // 发射事件
    this.eventStack.forEach(item => {
      item.name === name && item.callback(data);
    });
  }
}
复制代码

以上代码拷贝后就能够直接运用到项目中了,是否是很简单?

今后之后,妈妈不再用担忧我错过订阅事件的时机啦n(≧▽≦)n

相关文章
相关标签/搜索