mitt、tiny-emitter源码分析

312 阅读6分钟

1 简介

在学习 Vue 过程中,对于事件总线的使用并不少见,其基于发布/订阅方法,通常称为 Pub/Sub 然而,在 Vue3 不再推荐事件总线的使用,通过文章来了解 mitt、tiny-emitter 如何处理事件注册、注销、派发等功能

image.png


2 mitt

2.1 mitt 的使用

import mitt from 'mitt'

const emitter = mitt()

// listen to an event
emitter.on('foo', e => console.log('foo', e) )

// listen to all events
emitter.on('*', (type, e) => console.log(type, e) )

// fire an event
emitter.emit('foo', { a: 'b' })

// clearing all events
emitter.all.clear()

// working with handler references:
function onFoo() {}
emitter.on('foo', onFoo)   // listen
emitter.off('foo', onFoo)  // unlisten

2.2 mitt 源码分析

mitt 库,源码只有 119 行,整体分析起来比较简单,导出一个 mitt 函数,此函数可接受 all 参数,用于存储事件类型和事件函数映射,调用函数返回一个 emitter 对象,该对象包括all、on、off、emit 属性。
属性介绍:

  1. all 用于接收函数传入的 all 参数,如果不传,all 的值默认为 new Map()
  2. on(type,handler)on 为注册事件函数,type 为事件类型,handler 为处理函数,将会以 [handler] 数组的方式和 type 一起存储在 all 中,使用数组的方式存储,一个事件类型,可对应多个处理函数
  3. off(type,handler)off 为注销某个事件的某个处理函数,type 为事件类型,handler 为需注销的处理函数,如果不传,all 数组将删除事件类型对应的全部处理函数,如果传了,all 数组将只注销对应的处理函数
  4. emit(type,handler)emit 为派发函数,根据 type 找到对应的响应函数数组并逐一执行,handler 是传递给处理函数参数
// 导出 mitt 函数 ,调用函数返回对象,该对象包括all、on、off、emit 属性
export default function mitt<Events extends Record<EventType, unknown>>(
    all?: EventHandlerMap<Events>
): Emitter<Events> {
    type GenericEventHandler =
            | Handler<Events[keyof Events]>
            | WildcardHandler<Events>;
    // 存储事件类型和处理函数映射关系的数组,接收参数 all,如果没有,则新建 new Map()
    all = all || new Map();

    return {
        // 映射关系的数组
        all,
        // 注册函数
        on<Key extends keyof Events>(type: Key, handler: GenericEventHandler) {
            // 根据事件类型获取 all 中对应的处理函数数组
            const handlers: Array<GenericEventHandler> | undefined = all!.get(type);
            // 如果事件类型存在对应的处理函数数组
            if (handlers) {
            // 往处理函数数组中 push 新的处理函数
                handlers.push(handler);
            }
            else {
                // 不存在,这是第一次注册,往 all 中设置事件类型type 和 处理函数数组[handler]
                all!.set(type, [handler] as EventHandlerList<Events[keyof Events]>);
            }
        },
        // 注销函数
        off<Key extends keyof Events>(type: Key, handler?: GenericEventHandler) {
          // 根据事件类型获取 all 中对应的处理函数数组
          const handlers: Array<GenericEventHandler> | undefined = all!.get(type);
          // 如果事件类型存在对应的处理函数数组
          if (handlers) {
              // 如果存在需注销的处理函数 handler
              if (handler) {
                  // 删除指定的 handler 函数
                  // 无符号右移 index >>> 0 意义是:是保证x是有意义的数字类型,且为正整数
                  // 第一步将不是number类型的数据转换为number,并将number转换为无符号的32bit数据
                  // 第二步无符号的32bit数据(如果不能转换为Number,为0,如果为非整数,转换为整数)
                  // 第三步如果是正数,返回正数,如果是负数,返回二进制补码
                  // 例如:
                  // 1 >>> 0 为 1
                  // -1 >>> 0 为 4294967295
                  // 在二进制中,以补码存储
                  // 原码:一个整数,按照绝对值大小转换成的二进制数
                  // 反码:原码按位取反
                  // 补码:正数的补码就是原码,负数的补码是原码的反码再加1
                  // [+1] = [00000001]原 = [00000001]反 = [00000001]补
                  // [-1] = [10000001]原 = [11111110]反 = [11111111]补
                  handlers.splice(handlers.indexOf(handler) >>> 0, 1);
              }
              // 不存在,清空全部对应事件类型
              else {
                  all!.set(type, []);
              }
            }
        },
        // 派发函数
        emit<Key extends keyof Events>(type: Key, evt?: Events[Key]) {
            // 根据事件类型获取 all 中对应的处理函数数组
            let handlers = all!.get(type);
            // 如果事件类型存在对应的处理函数数组
            if (handlers) {
                // 依次执行处理函数,并传递参数
                (handlers as EventHandlerList<Events[keyof Events]>).slice()
                .map((handler) => {
                    handler(evt!);
                });
            }
            // 获取事件类型是否存在 * ,是否监听了全部事件
            handlers = all!.get('*');
            if (handlers) {
                // 依次执行处理函数,并传递参数
                (handlers as WildCardEventHandlerList<Events>).slice()
                .map((handler) => {
                    handler(type, evt!);
                });
            }
      }
  };
}

3 tiny-emitter

3.1 tiny-emitter 的使用

var Emitter = require('tiny-emitter');
var emitter = new Emitter();

emitter.on('some-event', function (arg1, arg2, arg3) {
 //
});

emitter.emit('some-event', 'arg1 value', 'arg2 value', 'arg3 value');

3.2 tiny-emitter 源码分析

tiny-emitter 库,源码只有 67 行,定义了一个 E 函数,在 prototype 上挂载了 on (name, callback, ctx)、 once (name, callback, ctx)、 emit (name)、 off (name, callback) 四个方法,并导出了 E

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)
}

// 定义注册、删除、派发方法在原型上,并通过返回 this 支持链式编程
E.prototype = {
  // 注册函数
  on: function (name, callback, ctx) {
    // 获取事件类型和处理函数映射关系对象,如果是首次调用,则赋值空对象
    var e = this.e || (this.e = {});
    // 获取 name 事件类型的处理函数数组,如果不存在,赋值 [],再 push 进去
    (e[name] || (e[name] = [])).push({
      fn: callback,
      ctx: ctx
    });
    return this;
  },
  // 注册一次调用函数
  once: function (name, callback, ctx) {
    // self 保存引用
    var self = this;
    function listener () {
      // 注销 name 对应的事件类型
      self.off(name, listener);
      // 执行派发
      // call作用:call方法的第一个参数代替 callback 方法内部的 this,其他参数为原 callback 方法的参数,
      callback.apply(ctx, arguments);
    };
    listener._ = callback
    return this.on(name, listener, ctx);
  },
  // 派发函数
  emit: function (name) {
    // [].slice === Array.prototype.slice 将类数组转换成数组
    // arguments 为类数组,可以使用for循环遍历,但是不能调用数组原型链的方法
    // call 方法第一个参数 arguments 指向 slice 方法内部 this,循环遍历 arguments,复制到新数组返回,这样就得到了一个复制arguments类数组的数组对象
    var data = [].slice.call(arguments, 1);
    // 根据事件类型获取中对应的处理函数数组
    var evtArr = ((this.e || (this.e = {}))[name] || []).slice();
    var i = 0;
    var len = evtArr.length;
    for (i; i < len; i++) {
      // apply 和 call 功能类似,区别为 apply(null,[1, 2, 3]),call(null,1, 2,3)
      // 执行派发函数,上下文绑定刚到派发函数的 this 中
      evtArr[i].fn.apply(evtArr[i].ctx, data);
    }
    return this;
  },
  // 注销函数
  off: function (name, callback) {
    var e = this.e || (this.e = {});
    // 事件处理函数数组
    var evts = e[name];
    // 注销事件后,还需继续处理函数的数组,由 callback 决定
    var liveEvents = [];
    // 如果存在处理函数数组
    if (evts && callback) {
      for (var i = 0, len = evts.length; i < len; i++) {
        // 如果不传递 callback 参数,将会取消订阅所有事件
        // 排除掉 callback 函数
        // evts[i].fn !== callback 对应 on
        // evts[i].fn._ !== callback 对应 once,evts[i].fn 为 listener,evts[i].fn._ 为 callback
        if (evts[i].fn !== callback && evts[i].fn._ !== callback)
          liveEvents.push(evts[i]);
      }
    }
    // 如果存在还需继续处理函数数组,则赋值到事件类型中,否则删除
    (liveEvents.length)
      ? e[name] = liveEvents
      : delete e[name];

    return this;
  }
};

4 mitt、tiny-emitter 对比

相同点

  • 支持 on、off、emit 方法

不同点

  • mitt
    • all 属性,通过 all 可以获取到事件类型对应的事件处理函数
    • 支持 * 监听,通过 * 可以监听所有的事件派发以及通过 Map clear 方法清除所有事件
    • 返回的是对象
    • 提供了 typescript支持
  • tiny-emitte
    • 支持链式调用
    • e 属性,通过 e 可以获取到事件类型对应的事件处理函数
    • once 方法,可以实现一次监听功能,并且可以通过上下文参数 ctx,用于设置 this
    • 返回的是一个函数

5 总结

通过阅读 mitt、tiny-emitter 源码,学习了如何实现事件的监听和派发,学习了无符号右移的应用,帮助自己温故而知新。