【若川视野 x 源码共读】第8期 | mitt、tiny-emitter 发布订阅

494 阅读5分钟

本文参加了由公众号@若川视野 发起的每周源码共读活动,点击了解详情一起参与。

tiny-emitter源码

github.com/scottcorgan…

function E () {
  // Keep this empty so it's easier to inherit from
  // (via https://github.com/lipsmack from https://github.com/scottcorgan/tiny-emitter/issues/3)
}

E.prototype = {
  on: function (name, callback, ctx) {
      var e = this.e || (this.e = {});
      // e.name = e.name ? e.name : e.name = [];
      // e.name.push({fn: callback, ctx})
      (e[name] || (e[name] = [])).push({
          fn: callback,
          ctx: ctx
      });

      return this;  // 为了可以链式调用
  },

  /** e.once('data', getData)  // 只emit一次
      e.emit('data')  // 第一次调用的时候已经删除了 绑定在对象上的事件
      e.emit('data')
      e.emit('data')
  */
  once: function (name, callback, ctx) {
      var self = this;
      function listener() {
          self.off(name, listener);  // 先删除
          callback.apply(ctx, arguments);  // 再调用
      };

      listener._ = callback  // 在listener函数上面绑一个_属性
      // 然后注册当前的事件,如果当前once绑定的事件 emit了,就会先删除,然后再调用,所以在第二次调用的时候,就不会在触发第二次了
      return this.on(name, listener, ctx);  
  },

  emit: function (name) {
    var data = [].slice.call(arguments, 1);  // 获取获取传入的其他参数
    // 做一下浅拷贝,防止在emit的时候。引用做了其他改变。
    // 如果once的时候已经删除了,这个数组就取不到值,自然就不能到循环,也不会调用函数了
    var evtArr = ((this.e || (this.e = {}))[name] || []).slice();  
    var i = 0;
    var len = evtArr.length;
    // 依次循环调用传入的函数,这个名字绑定了多少个,就会emit触发多少个
    for (i; i < len; i++) {
      evtArr[i].fn.apply(evtArr[i].ctx, data);
    }

    return this;
  },

  off: function (name, callback) {  // 删除当前绑定的函数
    var e = this.e || (this.e = {});
    var evts = e[name];  // 取出当前注册的队列
    var liveEvents = [];

    if (evts && callback) {  // 如果没有队列,或者没有传入callback,就不做任何处理,(理论上应该报个错吧)
      for (var i = 0, len = evts.length; i < len; i++) {  // 如果有就进行循环
        // 只要有一个没有命中,又不是 on 绑定的,也不是 once 绑定的,就说明啥也没有绑。
        if (evts[i].fn !== callback && evts[i].fn._ !== callback)
          liveEvents.push(evts[i]);
      }
    }

    // Remove event from queue to prevent memory leak  // 预防内存溢出,加了一步删除对象属性的工作
    // Suggested by https://github.com/lazd
    // 这个里面说:如果你总是在on的时候用push  在off 的时候用splice 可能会有意想不到的问题。然后作者说你说的对,改成了现在这种用arr收集的。这样就不会同时操作同一个引用。造成问题
    // Ref: https://github.com/scottcorgan/tiny-emitter/commit/c6ebfaa9bc973b33d110a84a307742b7cf94c953#commitcomment-5024910

    (liveEvents.length)
      ? e[name] = liveEvents  // 如果啥也没绑。就把收集的函数,在还给e[name]对象
      : delete e[name];  // 只要有一个命中就删除对应的对象,会整个数组都删除哦

    return this;
  }
};

module.exports = E;
module.exports.TinyEmitter = E;

1. Keep this empty so it's easier to inherit

注释里写了这样一句话。就是要把函数边空,让类更好继承,就不用非要写 call来处理this.e

因为使用当前发布订阅函数的时候,会继承使用, 可以使用es6继承

class Child extends E {
  constructor(){
    super()
  }
}

所以这里作者,用一个var e=this.e || (this.e = {})默认值,代替了声明在类的constructor中的this.e

2. 浅拷贝slice()

((this.e || (this.e = {}))[name] || []).slice(); 在emit的时候 作者做了浅拷贝,原因是:当前数组是一个引用。如果用户在emit的时候有对当前引用做其他操作,可能会有问题。

\

3. 数组引用删除

用splice删除会改变原数组引用,要谨慎使用。

mitt源码

github.com/developit/m…

export type EventType = string | symbol;

// An event handler can take an optional event argument
// and should not return a value
export type Handler<T = unknown> = (event: T) => void;
export type WildcardHandler<T = Record<string, unknown>> = (
type: keyof T,
  event: T[keyof T]
) => void;

// An array of all currently registered event handlers for a type
export type EventHandlerList<T = unknown> = Array<Handler<T>>;
export type WildCardEventHandlerList<T = Record<string, unknown>> = Array<WildcardHandler<T>>;

// A map of event types and their corresponding event handlers.
export type EventHandlerMap<Events extends Record<EventType, unknown>> = Map<
  keyof Events | '*',
  EventHandlerList<Events[keyof Events]> | WildCardEventHandlerList<Events>
>;

export interface Emitter<Events extends Record<EventType, unknown>> {
  all: EventHandlerMap<Events>;
  
  on<Key extends keyof Events>(type: Key, handler: Handler<Events[Key]>): void;
  on(type: '*', handler: WildcardHandler<Events>): void;
  
  off<Key extends keyof Events>(type: Key, handler?: Handler<Events[Key]>): void;
  off(type: '*', handler: WildcardHandler<Events>): void;
  
  emit<Key extends keyof Events>(type: Key, event: Events[Key]): void;
  emit<Key extends keyof Events>(type: undefined extends Events[Key] ? Key : never): void;
}

/**
* Mitt: Tiny (~200b) functional event emitter / pubsub.
* @name mitt
* @returns {Mitt}
*/
export default function mitt<Events extends Record<EventType, unknown>>(
all?: EventHandlerMap<Events>
): Emitter<Events> {
  type GenericEventHandler =
  | Handler<Events[keyof Events]>
  | WildcardHandler<Events>;
  all = all || new Map();
  
  return {
    
    /**
    * A Map of event names to registered handler functions.
    */
    all,
    
    /**
    * Register an event handler for the given type.
    * @param {string|symbol} type Type of event to listen for, or `'*'` for all events
    * @param {Function} handler Function to call in response to given event
    * @memberOf mitt
    */
    on<Key extends keyof Events>(type: Key, handler: GenericEventHandler) {
      /**
       * 下面的逻辑如果写成对象就是这样:对象的key只能是字符串,map的key可以用symbol function number 等等
       * all[type] ? all[type].push(handles) : all[type] = [handles]
       */
      const handlers: Array<GenericEventHandler> | undefined = all!.get(type);
      if (handlers) {
        handlers.push(handler);
      }
      else {
        all!.set(type, [handler] as EventHandlerList<Events[keyof Events]>);
      }
    },
      
      /**
      * Remove an event handler for the given type.
      * If `handler` is omitted, all handlers of the given type are removed.
      * @param {string|symbol} type Type of event to unregister `handler` from (`'*'` to remove a wildcard handler)
      * @param {Function} [handler] Handler function to remove
      * @memberOf mitt
      */
      off<Key extends keyof Events>(type: Key, handler?: GenericEventHandler) {
        const handlers: Array<GenericEventHandler> | undefined = all!.get(type);
        if (handlers) {
          if (handler) {
            handlers.splice(handlers.indexOf(handler) >>> 0, 1);
          }
          else {
            // 只传了type 会删除所有当前类型的函数
            all!.set(type, []);
          }
        }
      },
        
        /**
        * Invoke all handlers for the given type.
        * If present, `'*'` handlers are invoked after type-matched handlers.
        *
        * Note: Manually firing '*' handlers is not supported.
        *
        * @param {string|symbol} type The event type to invoke
        * @param {Any} [evt] Any value (object is recommended and powerful), passed to each handler
        * @memberOf mitt
        */
        emit<Key extends keyof Events>(type: Key, evt?: Events[Key]) {
          let handlers = all!.get(type);
          if (handlers) {  // 如果获取到了处理数组
            // 先拷贝,在处理,我理解用map,foreach应该都可以,可能map更符合函数式编程?
            (handlers as EventHandlerList<Events[keyof Events]>)
              .slice()
              .map((handler) => {
              handler(evt!);  // 函数的参数。没有做多参数处理,作者建议传个对象
            });
          }
          
          handlers = all!.get('*'); // 如果当前的type是全部, 监听所有的事件。不知道这有什么应用场景
          if (handlers) {
            (handlers as WildCardEventHandlerList<Events>)
              .slice()
              .map((handler) => {
              handler(type, evt!);
            });
          }
        }
};
}

1. Map 和 Object

简化一下on逻辑

all = new Map()
on(type, handler){
    // 如果有,就push到数组中,没有就初始化一个数组
    all.has(type) ? all.get(type).push(handler) : all.set(type, [handler])
}

如果on的代码用下面的对象写的话。

all = {}
on(type, handler){
    all[type] ? all[type].push(handler) : all[type] = [handler]
}
  1. 两个其实逻辑一致,不一样的就是map的key是可以用任意值(可以是function,symbol,object,number等)的,而obj的key只能是string
  2. 在内存占用和垃圾回收上的区别 (我也不太会。后面补 TODO)
  3. map中的clear() 等方法,都可以直接使用。all.clear()

2. 位运算

位运算:www.w3school.com.cn/js/js_bitwi…

无符号右移 缺位0来补

负数:-1 >>> 0 会得到2^32-1这个很大的数。就相当于没删除

正数:1 >>> 0 不会改变当前正数,所以就能找到当前function删除

有符号右移>>,缺位符号位来补,如果负数就补1 正数就补0

负数 最大 2 ^ 31

正数 最大 2 ^ 31 - 1 因为0 算在正数这边,占掉了一位

~ 取反

-n = ~n + 1

n = ~ -n + 1

| 只要有1 就为1

& 两个都为1 才为1

^ 相同为1 不同为0

Node中的events模块

node中的events模块,是node的核心模块

const Events = require('events');

基本api 有 on off emit once 还提供了一个newListener事件,监听绑定的事件。如果绑定事件会自动触发该函数,并返回绑定直接的name

function Events() {
    this._events = Object.create(null);
}

Events.prototype.on = function (name, fn) {
    if (!this._events) this._events = {};
    // 如果当前注册的应用不是newListener, 就会触发newListener,返回当前注册的事件名称
    /*
      events.on('newListener', (name) => { console.log(name) }) // 输出 data data1
      events.on('data', () => {})
      events.on('data1', () => {})
    */
    if (name !== 'newListener') {  
        this.emit('newListener', name)
    }
    (this._events[name] || (this._events[name] = [])).push(fn) 
}


Events.prototype.once = function (name, fn) {
    if (!this._events) this._events = {};
    const once =  (...args) => {  // 先触发,再删除
        fn(...args);
        this.off(name, once);
    }
    // 在once上绑定当前fn,不然删除不掉。这里因为包了一层函数,所以要让包的那层函数和之前的函数有点关系
    once._ = fn 
    this.on(name, once);
}

Events.prototype.emit = function (name, ...args) {
    if (!this._events) this._events = {};
    const handlers = this._events[name];

    if (handlers) {
        // 触发就是从数组中依次取出函数调用,与promise.then逻辑一致
        handlers.slice().forEach(fn => fn(...args)) 
    }
}

Events.prototype.off = function (name, fn) {
    if (!this._events) this._events = {};
    const handlers = this._events[name];

    if (handlers) {
      // 过滤掉,相等的。剩下的都是不相等的。
        this._events[name] = handlers.slice().filter(item => item !== fn && item._ !== fn)
    }
}

module.exports = Events;