xingbofeng / xingbofeng.github.io

counterxing的博客
https://xingbofeng.github.io
175 stars 18 forks source link

自己实现一个带权重的事件监听器 #33

Open xingbofeng opened 6 years ago

xingbofeng commented 6 years ago
/**
 * Create by https://github.com/xingbofeng at 2018-04-27
 * 支持自定义事件顺序的eventHub
 */

class EventHub {
  constructor() {
    this.listeners = {};
  }

  /**
   * _isValidListener:判断listener事件监听器是否合法
   * @param  {Function}  listener 事件监听器
   * @return {Boolean}            事件监听器合法性
   */
  _isValidListener(listener) {
    if (!listener) {
      return false;
    }
    const type = Object.prototype.toString.call(listener);
    if (type === '[object Function]' || type === '[object RegExp]') {
      return true;
    }
    if (type === '[object Object]') {
      return this._isValidListener(listener.listener);
    }
    return false;
  }

  /**
   * _isValidEventName:判断eventName是否合法
   * @param  {String}  eventName 事件名称
   * 事件名称举例:
   * 1. 'event':注册一个名为event的事件,权重级别为最低,即-Infinity
   * 2. 'event.1':注册一个名为event的事件,权重级别为1
   * 3. 'event.1.1':注册一个名为event的事件,权重级别为1.1
   * 4. 'event.1.1.1':不合法
   * 5. 'event.e':合法,注册一个名为event.e的事件,权重级别为-Infinity,但不推荐,不能为此类事件增加权重
   * @return {Boolean}           [description]
   */
  _isValidEventName(eventName) {
    if (!eventName) {
      return false;
    }
    const type = Object.prototype.toString.call(eventName);
    const reg = /^([^\.]+\.(\-|\+)?(\d+\.)?\d+)$|^([^\.]+)$/;
    if (type === '[object String]' && reg.test(eventName)) {
      return true;
    }
    return false;
  }

  /**
   * _stricterParseFloat:用于严格的parseFloat,因为原生的parseFloat不支持对于-Infinity和Infinity的转换
   * @param  {String} value 传入需要转换的字符串
   * @return {Number}       返回float转换的参数
   */
  _stricterParseFloat(value) {
    if (/^(\-|\+)?([0-9]+(\.[0-9]+)?|Infinity)$/
      .test(value))
      return Number(value);
    return NaN;
  }

  /**
   * _findIndexOfListeners:用于$off方法,找到一个权重的事件监听器集合中的指定事件监听器下标
   * @param  {Array} priorityListeners  指定权重事件监听器集合
   * @param  {Function} listener        事件监听器
   * @return {Number}                   事件监听器下标,如果没找到则返回-1
   */
  _findIndexOfListeners(priorityListeners, listener) {
    return priorityListeners.findIndex((value) => value === listener);
  }

  /**
   * $once:注册一次性的事件监听器,事件名称规则类似于$emit
   * @param  {String} eventName   事件名称
   * @param  {Function} listener  事件监听器
   * @return {Object}             返回this以便链式调用
   */
  $once(eventName, listener) {
    listener._once = true;
    return this.$on(eventName, listener);
  }

  /**
   * $off:取消事件监听器
   * @param  {String} eventName   事件名称
   * @param  {Function} listener  事件监听器函数
   * @return {Object}             返回this以便链式调用
   */
  $off(eventName, listener) {
    if (!this.listeners[eventName]) {
      return;
    }
    // 拿到该事件的所有事件监听器,这个监听器为一个对象,key为其权重
    const originEventListeners = this.listeners[eventName];

    // 遍历所有该事件名下的所有权重的listeners
    for (let priority in originEventListeners) {
      if (originEventListeners.hasOwnProperty(priority)) {
        // listeners为任意权重的事件监听器集合
        const priorityListeners = originEventListeners[priority];
        let index = this._findIndexOfListeners(priorityListeners, listener);
        if (index === -1) {
          continue;
        } else {
          priorityListeners.splice(index, 1);
        }
      }
    }

    // 返回this以便链式调用
    return this;
  }

  /**
   * $emit:触发一个事件
   * @param  {String}    eventName 触发一个事件
   * @param  {...Array}  args      事件传递的参数
   * @return {Object}              返回this以便链式调用
   */
  $emit(eventName, ...args) {
    if (!this.listeners[eventName]) {
      return;
    }
    // 拿到该事件的所有事件监听器,这个监听器为一个对象,key为其权重
    const originEventListeners = this.listeners[eventName];
    // 遍历所有的当前listeners,并且保证必须遍历到为自己添加的具有权重listener,没有权重的listener并不遍历
    let prioritys = Object.keys(originEventListeners)
      .filter((priority) => Object.prototype.toString.call(priority) === '[object String]')
      .sort((a, b) => this._stricterParseFloat(b) - this._stricterParseFloat(a));

    // 排序后的事件监听队列
    let listeners = [];
    // 开始组装事件监听队列
    prioritys.forEach((priority) => {
      listeners.push(...originEventListeners[priority]);
    });
    // 拿到了排序后的事件监听队列
    listeners.forEach((listener) => {
      listener.apply(this, args);
      // 如果是once触发,则触发完成后off
      if (listener._once) {
        this.$off(eventName, listener);
      }
    });

    // 返回this以便链式调用
    return this;
  }

  /**
   * $on:监听一个事件
   * @param  {String} eventName   事件名称
   * 事件名称举例:
   * 1. 'event':注册一个名为event的事件,权重级别为最低,即-Infinity
   * 2. 'event.1':注册一个名为event的事件,权重级别为1
   * 3. 'event.1.1':注册一个名为event的事件,权重级别为1.1
   * 4. 'event.1.1.1':不合法
   * 5. 'event.e':合法,注册一个名为event.e的事件,权重级别为-Infinity,但不推荐,不能为此类事件增加权重
   * @param  {Function} listener  事件监听器
   * @return {Object}             返回this以便链式调用
   */
  $on(eventName, listener) {
    if (!this._isValidListener(listener)) {
      throw new TypeError('listener is not valid, must be a funtion or valid object');
    }
    if (!this._isValidEventName(eventName)) {
      throw new TypeError('eventName is not valid, must be valid static string or string which includes eventName and priority, such as `event` or `event.1` or `event.1.1`');
    }
    let priority, event;
    const reg = /^([^\.]+\.(\-|\+)?(\d+\.)?\d+)$/;
    // 如果是具备有权重的eventName
    if (reg.test(eventName)) {
      event = /^[^\.]+/.exec(eventName)[0]
      priority = eventName.split(/^[^\.]+\./)[1];
    } else {
      event = eventName;
      // 如果没有权重参数传入,则把传入的eventName作为事件名称,权重为负无穷
      priority = -Infinity;
    }

    if (!this.listeners[event]) {
      this.listeners[event] = {
        [priority]: [listener],
      };
    } else {
      if (!this.listeners[event][priority]) {
        this.listeners[event][priority] = [listener];
      } else {
        this.listeners[event][priority].push(listener);
      }
    }
    // 返回this以便链式调用
    return this;
  }
}

const eventHub = new EventHub();

const eventListener = () => console.log('event')

eventHub.$on('event', eventListener);
eventHub.$on('event.2', () => console.log('event.2'));
eventHub.$on('event.1', () => console.log('event.1'));
eventHub.$on('event.-1', () => console.log('event.-1'));

console.log(eventHub);

eventHub.$emit('event');
eventHub.$off('event', eventListener);

console.log(eventHub);

eventHub.$emit('event');

eventHub.$once('event.1', () => console.log('once'));
eventHub.$emit('event');