前言
EventEmitter,广泛的应用于javascript语言中,浏览器事件(如鼠标单击click,键盘事件keyDown)都是该模式的例子。我们在编写代码时也经常用它来解耦,比如:组件间我们不想通过大量状态来通信时,可以考虑用它来编写代码。不仅如此,面试时可能面试官会让我们实现一个EventEmitter,这时候可能有很多人就不会了。
网上已经有很多EventEmitter的实现了,那我为啥还要写一个;首先,看一百次不如自己动手写一次;其次,这个模块很常用,自己定制的话也容易根据实际业务情况的变动做修改。
代码实现
以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);
}
复制代码上面有两点需要注意下,首先,每个事件的初始值是一个数组,因为对于一个事件,我们可能会做多件事;
其次,监听事件一次,只是在调用监听事件方法时多加了一个参数,至于为什么,我们看看后面的触发事件方法就能明白。
触发事件
// 触发事件
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;
}
复制代码这里的取消事件监听分了几种不同情况,根据参数去判断。
完整代码
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;
  }
}
复制代码看完我的,希望大家也能动手实现一个,自己写的才更不容易忘记。
© 版权声明
文章版权归作者所有,未经允许请勿转载。
THE END
    





















![[桜井宁宁]COS和泉纱雾超可爱写真福利集-一一网](https://www.proyy.com/skycj/data/images/2020-12-13/4d3cf227a85d7e79f5d6b4efb6bde3e8.jpg)

![[桜井宁宁] 爆乳奶牛少女cos写真-一一网](https://www.proyy.com/skycj/data/images/2020-12-13/d40483e126fcf567894e89c65eaca655.jpg)
