1 简介
在学习 Vue 过程中,对于事件总线的使用并不少见,其基于发布/订阅方法,通常称为 Pub/Sub 然而,在 Vue3 不再推荐事件总线的使用,通过文章来了解 mitt、tiny-emitter 如何处理事件注册、注销、派发等功能
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 属性。
属性介绍:
- all 用于接收函数传入的 all 参数,如果不传,all 的值默认为 new Map()
- on(type,handler)on 为注册事件函数,type 为事件类型,handler 为处理函数,将会以 [handler] 数组的方式和 type 一起存储在 all 中,使用数组的方式存储,一个事件类型,可对应多个处理函数
- off(type,handler)off 为注销某个事件的某个处理函数,type 为事件类型,handler 为需注销的处理函数,如果不传,all 数组将删除事件类型对应的全部处理函数,如果传了,all 数组将只注销对应的处理函数
- 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 源码,学习了如何实现事件的监听和派发,学习了无符号右移的应用,帮助自己温故而知新。