Mitt 一个小而全的发布订阅库,麻雀虽小五脏俱全

296 阅读6分钟

Mitt 是什么

Tiny 200b functional event emitter / pubsub.

  • Microscopic:  weighs less than 200 bytes gzipped
  • Useful:  a wildcard "*" event type listens to all events
  • Familiar:  same names & ideas as Node's EventEmitter
  • Functional:  methods don't rely on this
  • Great Name:  somehow mitt wasn't taken

Mitt was made for the browser, but works in any JavaScript runtime. It has no dependencies and supports IE9+.

这是官方作者 Jason Miller 对该库最直白的介绍: 我们看到:他说了包的体积非常小 gzip 后 200b ,支持通配符监听所有的事件, 跟nodejs 中的 eventEmiter 使用方式类似;不再依赖this; 关于这个名字 mitt 也是很随意,没有什么特别,仅仅因为它没有被使用过;看来命名难的问题全球都存在, hhh 。

Mitt 专门为浏览器环境制作的,但是它可以工作在任意的js 运行环境,没有任何依赖项,并且支持IE9+(为什么要突出IE9,难道国外也很多IE党)

它的周下载量:580万+ 看样子非常的火爆。

解决了什么问题

那顾名思义它指定是解决了 发布/订阅的问题;

我们简单的了解一下它的使用方式


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

这是一段示例代码,简直平平无奇;

是不是和我们在vue2 中使用的 emit 事件大差不差 emit ,on , once 这些;

再看看 nodejs 中的 eventEmiter 呢

//event.js 文件
var events = require('events'); 
var emitter = new events.EventEmitter(); 
emitter.on('someEvent', function(arg1, arg2) { 
    console.log('listener1', arg1, arg2); 
}); 
emitter.on('someEvent', function(arg1, arg2) { 
    console.log('listener2', arg1, arg2); 
}); 
emitter.emit('someEvent', 'arg1 参数', 'arg2 参数'); 

不说一模一样,基本都是这个样子

那在vue2 中我们经常使用 EventBus 事件总线的方式来处理一些全局的事件监听等; EventBus 的本质其实也是发布订阅模式

但是在vue3 中 官方已经移除了这个,推荐使用今天我们说的 mitt 。

既然能vue 官方推荐,那看样子应该是很好用的;

下面我们就来看看它到底怎么样呢

源码分析

我们来 看看v3.0.1 它的源码


// 定义了EventType的类型为 String 或 Symbol
export type EventType = string | symbol;

// 定义了事件处理函数 handle ,接受一个 可选的event类型的参数 没有返回值
export type Handler<T = unknown> = (event: T) => void;
// 定义了通配事件处理函数,接受 类型,和 事件参数
export type WildcardHandler<T = Record<string, unknown>> = (
	type: keyof T,
	event: T[keyof T]
) => void;

// 定义了一个数组用于存放已经注册的事件处理函数
export type EventHandlerList<T = unknown> = Array<Handler<T>>;
// 同上 也是定义了一个数组用于存放 WildcardHandler 事件
export type WildCardEventHandlerList<T = Record<string, unknown>> = Array<
	WildcardHandler<T>
>;

// 定义一个map 来 存储事件的key和事件函数对应,简单点说就是 一个事件名称对应的事件处理函数列表,这里也包含了通配事件。
export type EventHandlerMap<Events extends Record<EventType, unknown>> = Map<
	keyof Events | '*',
	EventHandlerList<Events[keyof Events]> | WildCardEventHandlerList<Events>
>;
// 定义了 emiter api , 包含了 all , on , off , emit 这些 api , 也正是我们上方示例代码用到的这些;
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;
}

我们可以看出,这一段代码都是做了很多定义。并没有具体的逻辑,这也算磨刀不误砍柴工了。

下面我们看具体的逻辑实现:


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 {
                 // 已经注册的所有事件处理函数的Map 
		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) {
                        // 这里先查看事件名对应的事件处理函数集合 
			const handlers: Array<GenericEventHandler> | undefined = all!.get(type);
                        // 存在集合就继续往集合加一个
			if (handlers) {
				handlers.push(handler);
			} else {
                             // 不存在集合 就创建一个集合,并吧当前的handle 存进去
				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) {
                            // 如果指定了 handler 就删除
                            if (handler) {
// 这里使用了位运算符,">>>" 后面我们具体介绍一下这个 位运算符,
handlers.splice(handlers.indexOf(handler) >>> 0, 1);
				} else {
                                        // 没有传入 handler 就全部清空
					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) {
                                // 一个个处理执行 处理函数。
				(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!);
					});
			}
		}
	};
}

这就是它的全部源码,代码结构简单,实现逻辑清晰。

我们回顾学习一下 >>> 这个位运算

它把无符号的 32 位整数所有数位整体右移。对于无符号数或正数右移运算,无符号右移与有符号右移运算的结果是相同的。

下面两行表达式的返回值是相同的。

console.log(1000 >> 8); // 返回值 3
console.log(1000 >> 8); // 返回值 3

对于负数来说,无符号右移将使用 0 来填充所有的空位,同时会把负数作为正数来处理,所得结果会非常大所以,使用无符号右移运算符时要特别小心,避免意外错误。

console.log(-1000 >> 8); // 返回值 -4
console.log(-1000 >>> 8); // 返回值 16777212

你学会了吗

看到这里是不是觉得如此简单,是不是突然觉得就这 ? 那我也行

那这其实就是一个发布订阅的基础框架结构。我们可以参考这个基础框架结构自己写一个:

来吧展示:


class Pubsub{

   constructor(){
      this.handles={};
   }

   on(eventType,handle){
      if(!this.handles.hasOwnProperty(eventType)){
         this.handles[eventType]=[];
      }

      if(typeof handle === 'function'){
         this.handles[eventType].push(handle);
      }else{
         throw new Error(`${eventType}`+'缺少回调函数')
      }

      return this;
   }

   emit(eventType,...args){
      if(this.handles.hasOwnProperty(eventType)){
         this.handles[eventType].forEach((item,key,arr)=>{
            item.apply(null,args);
         });
      }else{
         throw new Error(`"${eventType}"事件未注册`);
      }

      return this;
   }

   off(eventType,handel){
      if(!this.handles.hasOwnProperty(eventType)){
         throw new Error(`"${eventType}"事件未注册`);
      }else if(typeof handel !=='function'){
         throw new Error('缺少回调函数')
      }else{
         this.handles[eventType].forEach((item,index,arr)=>{
            if(item === handel){
               arr.splice(index,1)
            }
         })
      }

      return this;
   }
}

// eg. 
let callback = function(argument) {
   console.log('you are so nice');
}

let pubsub = new Pubsub();

pubsub.on('completed',(...args)=>{
   console.log(args.join(' '))
}).on('completed',callback);


pubsub.emit('completed','hello','world')

pubsub.off('completed',callback);

pubsub.emit('completed','yeah','nice to meet you')

😂 是不是 就学会了呢

最后的最后

下面请上我们今天的主角:有请小趴菜

小趴菜.jpeg