被寫爛系列——實現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

相關文章
相關標籤/搜索