mitt 源码解析

165 阅读3分钟

mitt 是只有 200b 的 EventBus 库,可以在任何 javascript 运行时环境中运行,没有任何依赖,支持 IE9+。源码很精炼,可以在 GitHub 的 src/index.ts 文件中找到源代码。

主体分析

整体就是一个工厂函数,返回一个对象,这个对象包含 all 属性以及on,off,emit 三个方法。all 默认是从 mitt 函数的传参中取值,没有传参则创建一个 map。

export default function mitt<Events extends Record<EventType, unknown>>(
	all?: EventHandlerMap<Events>
): Emitter<Events> {
	all = all || new Map();

	return {
	all,
        on(){},
        off(){},
        emit(){},
	};
}

on 方法支持传入 type 和 handler 参数,使用 all.get(type) 可以获取到 handlers。如果 handlers 有值则将 handler 放到 handlers 里面,否则将 type 作为键,[handler] 作为值保存到 all 里面。

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

off 方法可以传入两个参数,一个必填参数 type,另一个是可选参数 handler,从 all 可以获取到键为 type 的 handlers。如果 handlers 有值则继续进行判断,handler 有值则从 handlers 中找到该值并移除,否则 all 将保存键为 type,值为 [] 的情况。这里需要提下 handlers.indexOf(handler) >>> 0,由于 indexOf 存在值为-1而 splice 支持负数的情况,所以这里使用了无符号右移 -1>>>0 的结果是 4294967295,这样 splice 的操作不影响数组

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, []);
        }
    }
},

emit 方法可以传入一个必填参数 type 和一个可选参数 evt。从 all 中获取到 type 键的 handlers,有值的话进行浅拷贝,将 evt 作为参数遍历调用里面的函数。也支持从 all 中获取到 * 键的 handlers,有值的话进行浅拷贝,将 type 和 evt 作为参数遍历调用里面的函数。

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

清除的时候可以利用 map 自身的方法,也就是 all.clear() 即可清除里面的所有键值对

类型分析

mitt 还有完善的类型支持。EventType 是联合类型 string|symbol。WildcardHandler 相比 Handler 增加了一个 type,它需要满足一个键为 string 的范型,也就是 type 必须是范型的键,event 为范型的值。EventHandlerMap 为 all 的类型注解,键值都包含两种情况。Emitter 接口列举了 mitt 所有会出现的属性和方法,其中 on,off,emit 使用了函数重载,每个方法都有两种情况。

export type EventType = string | symbol;

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>>;
export type WildCardEventHandlerList<T = Record<string, unknown>> = Array<WildcardHandler<T>>;

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 库源代码较少,但是功能和类型都已具备。除了上面分析的功能和类型,项目代码也有很多值得学习的地方,比如测试代码,里面详细例举了多种情况,代码行数甚至比源代码还要多。

参考资料: 无符号右移