被写烂系列——实现EventEmitter

前言

EventEmitter,普遍的应用于javascript语言中,浏览器事件(如鼠标单击click,键盘事件keyDown)都是该模式的例子。咱们在编写代码时也常常用它来解耦,好比:组件间咱们不想经过大量状态来通讯时,能够考虑用它来编写代码。不只如此,面试时可能面试官会让咱们实现一个EventEmitter,这时候可能有不少人就不会了。javascript

网上已经有不少EventEmitter的实现了,那我为啥还要写一个;首先,看一百次不如本身动手写一次;其次,这个模块很经常使用,本身定制的话也容易根据实际业务状况的变更作修改。java

代码实现

以Nodejs 的 EventEmitter API为参考,咱们大概要实现如下的API:面试

  • on() // 监听事件
  • once() // 监听事件一次
  • emit() // 触发事件
  • off() // 取消监听
  • getEvents() // 获取全部的监听事件

首先咱们须要存储全部监听事件的地方,那么如何存储呢?常见的方法是使用哈希表,由于时间复杂度是 O(1),空间复杂度通常也不会太大。因此咱们的存储方式是:数组

interface EventType {
  readonly callback: Function;
  readonly once: boolean;
}

interface EventMap {
  [propName: string]: EventType[]
}

export default class EventEmitter {
  private eventMap: EventMap = {};
}
复制代码

接下来就是实现具体的方法浏览器

监听事件

// 监听事件
on(event: string, callback: Function, once?: boolean) {
  if (!this.eventMap[event]) {
    this.eventMap[event] = []; 
  }
  this.eventMap[event].push({
    callback,
    once: !!once,
  });
  return this;
}

// 监听事件一次
once(event: string, callback: Function) {
  return this.on(event, callback, true);
}
复制代码

上面有两点须要注意下,首先,每一个事件的初始值是一个数组,由于对于一个事件,咱们可能会作多件事; 其次,监听事件一次,只是在调用监听事件方法时多加了一个参数,至于为何,咱们看看后面的触发事件方法就能明白。markdown

触发事件

// 触发事件
emit(event: string, ...args: any[]) {
  const events = this.eventMap[event] || [];
  let length = events.length;
  
  for (let i = 0; i < length; i++) {
    if (!events[i]) {
      continue;
    }
    const { callback, once } = events[i];

    if (once) {
      events.splice(i, 1);

      if (events.length === 0) {
        delete this.eventMap[event];
      }

      length--;
      i--;
    }

    callback.apply(this, args);
  }
}
复制代码

这里就对两种不一样状况下的事件监听作了不一样处理。app

取消事件监听

// 取消事件监听
off(event?: string, callback?: Function) {
  if(!event) {
    // event 为空所有清除
    this.eventMap = {}
  } else {
    if(!callback) {
      // event 存在,但callback不存在
      delete this.eventMap[event]
    } else {
      // event 存在,callback 存在,清除匹配的方法
      const events = this.eventMap[event] || [];
      let length = events.length;

      for (let i = 0; i < length; i++) {
        if (events[i].callback === callback) {
          events.splice(i, 1);
          length--;
          i--;
        }
      }

      if (events.length === 0) {
        delete this.eventMap[event];
      }
    }
  }

  return this;
}
复制代码

这里的取消事件监听分了几种不一样状况,根据参数去判断。flex

完整代码

interface EventType {
  readonly callback: Function;
  readonly once: boolean;
}

interface EventMap {
  [propName: string]: EventType[]
}

export default class EventEmitter {
  private eventMap: EventMap = {};

  // 监听事件
  on(event: string, callback: Function, once?: boolean) {
    if (!this.eventMap[event]) {
      this.eventMap[event] = [];
    }
    this.eventMap[event].push({
      callback,
      once: !!once,
    });
    return this;
  }

  // 监听事件一次
  once(event: string, callback: Function) {
    return this.on(event, callback, true);
  }

  // 触发事件
  emit(event: string, ...args: any[]) {
    const events = this.eventMap[event] || [];
    let length = events.length;
    for (let i = 0; i < length; i++) {
      if (!events[i]) {
        continue;
      }
      const { callback, once } = events[i];

      if (once) {
        events.splice(i, 1);

        if (events.length === 0) {
          delete this.eventMap[event];
        }

        length--;
        i--;
      }

      callback.apply(this, args);
    }
  }

  // 取消事件监听
  off(event?: string, callback?: Function) {
    if(!event) {
      // event 为空所有清除
      this.eventMap = {}
    } else {
      if(!callback) {
        // event 存在,但callback不存在
        delete this.eventMap[event]
      } else {
        // event 存在,callback 存在,清除匹配的方法
        const events = this.eventMap[event] || [];
        let length = events.length;

        for (let i = 0; i < length; i++) {
          if (events[i].callback === callback) {
            events.splice(i, 1);
            length--;
            i--;
          }
        }

        if (events.length === 0) {
          delete this.eventMap[event];
        }
      }
    }

    return this;
  }

  // 获取当前全部的事件
  getEvents() {
    return this.eventMap;
  }
}
复制代码

看完个人,但愿你们也能动手实现一个,本身写的才更不容易忘记。this

相关文章
相关标签/搜索