天天看點

【源碼閱讀】一個隻有200b的javascript釋出訂閱庫-mitt

作者:橡樹果前端工作室

介紹

Tiny 200b functional event emitter / pubsub.

200b大小的微型功能事件釋出/訂閱庫

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

Mitt是為浏覽器設計的,但可以在任意JavaScript運作時使用,無依賴且支援IE9+。

一般在現代架構跨元件通信時會比較常用,例如在vue跨元件通信時會比較有用,在vue2中可以使用自帶的 $on,$off,$emit 等api進行通信,當在vue3中移除了這些api,取而代之的有 mitt 或者 tiny-emitter 。

install

npm install --save mitt           
  • ES6 Modules
import mitt from "mitt"           
  • CommonJs Modules
const mitt = require("mitt");           
  • CDN
<script src="https://unpkg.com/mitt/dist/mitt.umd.js"></script>
// 然後在wndow上通路
window.mitt           

Usage

  • 基本使用
import mitt from 'mitt';

const emitter = mitt();
// 監聽事件
emitter.on('foo', e => console.log('foo', e) );
// 解除監聽
emitter.off('foo', onFoo);
// 監聽全部事件
emitter.on('*', (type, e) => console.log(type, e) );
// 觸發事件
emitter.emit('foo', { a: 'b' });
// 清除所有事件監聽
emitter.all.clear()           
  • typescript支援
import mitt from 'mitt';

type Events = {
  foo:  string;
  bar?:  number;
};

const emitter = mitt<Events>();

emitter.on('foo', (e) => {}); // e的類型為string           
  • 簡單的封裝
// mitt.ts
import mitt from "mitt";

type Events = {
  change: string | undefined;
  submit: number[]
}

const emitter = mitt<Events>();

export default emitter;

// 元件 A
<script setup lang="ts">
import Emitter from "@/shared/mitt";
Emitter.emit("submit", [1]);
</script>

// 元件B
<script setup lang="ts">
import Emitter from "@/shared/mitt";
Emitter.on("submit", (params) => {
  // params 的類型 => number[]
  console.log(params);
})
</script>           

源碼解析

完整的源碼如下

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) {
			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 {
					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!);
					});
			}
		}
	};
}           

可以看到,整個庫暴露的api隻有以下4個

all,on(),off(),emit()

接下來了解一下每個api的實作

  • all

從源碼可以看出 all 是一個 Map 對象,用來存儲所有的事件,如果初始化沒有傳入會自動建立一個空的 Map 對象,是以支援所有 Map 的方法,如:

// 擷取監聽函數
Emitter.all.get(key); 
// 設定監聽事件
Emitter.all.set(key, [handler]);
// 清除所有事件
Emitter.all.clear();           
  • on
on<Key extends keyof Events>(type: Key, handler: GenericEventHandler) {
	const handlers: Array<GenericEventHandler> | undefined = all!.get(type);
  if (handlers) {
    handlers.push(handler);
  }
  else {
    all!.set(type, [handler] as EventHandlerList<Events[keyof Events]>);
  }
},           

on() 用于監聽事件,接收兩個參數:

  • type 事件名稱
  • handler 回調函數
Emitter.on(type, handler);           

在函數内部,首先擷取對應 type 的 handler 清單,如果存在,則向 handlers 中 push

const handlers: Array<GenericEventHandler> | undefined = all!.get(type);
if (handlers) {
  handlers.push(handler);
}           

如果不存在,則設定對應事件處理回調

all!.set(type, [handler] as EventHandlerList<Events[keyof Events]>);           
  • off
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 {
      all!.set(type, []);
    }
  }
}           

off() 用于移除事件監聽,接收兩個參數:

  • type 事件名稱
  • handler 要移除的回調函數,可選

首先擷取對應 type 的 handler 清單,然後判斷 handlers 是否存在,如果此時傳入了 handler,則删除對應的 handler

if (handler) {
  handlers.splice(handlers.indexOf(handler) >>> 0, 1);
}           

需要注意的是,這裡使用了 無符号右移運算符(>>>)(具體用法請查閱MDN),且移位了 0,對于 非負數 來說,進行該運算沒有任何作用,而如果是 -1 >>> 0,其運算結果就是4294967295,而 splice 方法如果傳入的 start > array.length,并不會删除任何元素,其實就是省略了一步判斷而已,使代碼更精簡,相當于如下代碼

let index = handlers.indexOf(handler);
if (index > -1) {
	handlers.splice(index, 1);
}           

如果沒有傳入 handler,則清空對應的監聽清單

all!.set(type, []);           
  • emit
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!);
      });
  }
}           

emit() 用于觸發事件監聽,接收兩個參數:

  • type 事件名稱
  • evt 傳入回調的參數

emit() 的邏輯就是擷取對應的 handlers,然後循環執行回調,并且會通知 type = * 的訂閱回調

以上就是本文的全部内容了,如果覺得有用的話,可以關注作者哦!

本文為原創作品,未經同意禁止轉載!

繼續閱讀