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')
😂 是不是 就学会了呢
最后的最后
下面请上我们今天的主角:有请小趴菜