JS案例:基于发布订阅实现的事件消息中心-MessageCenter
目录
前言
其中简单描述了一下JavaScript中发布订阅模式的实现,但是个人觉得其中的方法过于单一,于是想尝试拓展一下,做个简易版的消息中心
起步
参考node中的 events事件触发器 我总结归类出了以下函数
- on :注册事件
- emit:触发事件
- un:事件销毁
- once:注册事件,执行后即销毁
- clear:重置事件列表(消息中心)
- has:判断事件是否被订阅
- handlerLength:返回某个事件的监听函数数量
- watch:与on一样,不同点是可以将结果返回至发布者
- invoke:与emit一样,配合watch使用,当watch中存在异步操作时接收其结果
功能设计
了解了实现的功能,我们把类的接口实现一下,其中events是之前文章中的调度中心,使用一个对象来存取所有绑定的事件
export declare interface Handlers {
[key: string]: Array<Function>
}
export declare interface IMessageCenter {
events: Handlers
_instance?: IMessageCenter
on: (type: string, handler: Function) => this
emit: (type: string, data?: any) => this
un: (type: string, handler?: Function) => this
once: (type: string, handler: Function) => this
clear: () => this
has: (type: string) => boolean
handlerLength: (type: string) => number
watch: (type: string, handler: Function) => this
invoke: (type: string, data?: any) => Promise<void>
}
工具函数实现
接口完成后,我们来写一些工具函数,这些函数不需要暴露在外面所以在类中,比如异常处理函数:用于解析参数是否异常;单例函数:返回当前类的实例的单例;批量执行函数:执行events中与事件名绑定的函数列表;批量销毁函数:批量销毁调度中心中某个函数集;此外,还有一个函数用于区别watch、invoke和on、emit的事件类型(type)的字符串混入。
异常处理函数:
/**
* 检查参数是否符合标准
* @param type 事件名
* @param handler 事件钩子
*/
private checkHandler(type: string, handler: Function) {
if (type?.length === 0) {
throw new Error('type.length can not be 0')
}
if (!handler || !type) {
throw new ReferenceError('type or handler is not defined')
}
if (typeof handler !== 'function' || typeof type !== 'string') {
throw new TypeError(`${handler} is not a function or ${type} is not a string`);
}
}
单例函数:
//返回当前类的实例的单例
static Instance(Fn) {
if (!Fn._instance) {
Object.defineProperty(Fn, "_instance", {
value: new Fn()
});
}
return Fn._instance;
}
批量执行函数:
// 批量执行调度中心中某个函数集
private runHandler(type, data) {
for (let i = 0; i < this.events[type].length; i++) {
this.events[type][i] && this.events[type][i](data)
}
}
批量销毁函数:
// 批量销毁调度中心中某个函数集
private unHandler(type, handler) {
!handler && (this.events[type] = [])
handler && this.checkHandler(type, handler)
for (let i = 0; i < this.events[type].length; i++) {
if (this.events[type][i] && this.events[type][i] === handler) {
this.events[type][i] = null
}
}
}
字符串混入:
private prefixStr(str) {
return `@${str}`
}
消息中心类实现
工具函数实现完成后,我们就可以正式开始实现接口中定义的各种函数了,以下是函数的实现过程,其中this.events是事件调度中心,一个以事件type为key的对象
- has:
// 判断事件是否被订阅
has(type: string) {
return !!this.events[type]
}
- on:
/**
* 注册事件至调度中心
* @param type 事件类型,特指具体事件名
* @param handler 事件注册的回调
*/
on(type, handler) {
this.checkHandler(type, handler)
if (!this.has(type)) { //若调度中心未找到该事件的队列,则新建某个事件列表(可以对某个类型的事件注册多个回调函数)
this.events[type] = []
}
this.events[type].push(handler)
return this
}
- emit:
/**
* 触发调度中心的某个或者某些该事件类型下注册的函数
* @param type 事件类型,特指具体事件名
* @param data 发布者传递的参数
*/
emit(type, data) {
if (this.has(type)) {
this.runHandler(type, data)
}
return this
}
- un:
//销毁监听
un(type, handler) {
this.unHandler(type, handler)
return this
}
- once:
// 只注册一次监听,执行即销毁
once(type, handler) {
this.checkHandler(type, handler)
const fn = (...args) => {
this.un(type, fn);
return handler(...args)
}
this.on(type, fn)
return this
}
- clear:
// 重置调度中心
clear() {
this.events = {}
return this
}
- handlerLength:
// 一个事件被绑定了多少函数
handlerLength(type: string) {
return this.events[type]?.length ?? 0
}
- watch:
// 监听invoke的消息,若handler中进行了计算或者异步操作,会反馈给invoke
watch(type, handler) {
this.checkHandler(type, handler)
const fn = (...args) => {
this.emit(this.prefixStr(type), handler(...args));
}
this.on(type, fn);
return this
}
- invoke:
// 触发watch事件,并且接收watch处理结果
invoke(type, data) {
return new Promise<void>((resolve) => {
this.once(this.prefixStr(type), resolve);
this.emit(type, data);
})
}
验证功能
实现完成后,我们试试效果
on=>emit,和之前一样,on监听一个或多个事件,emit触发该事件名下所有事件
function funcA(args) {
console.log(args)
}
function funcB(args) {
console.log(++args.count);
}
messageCenter.on("a", funcA);
messageCenter.on("a", funcB);
messageCenter.emit("a", { count: 1 }); // { count: 1 } 2
messageCenter.emit("a", { count: 2 }); // { count: 2 } 3
on=>emit=>un=>emit,on监听事件,un销毁事件且不再允许emit当前函数,若不传函数,则清除当前type(事件名)下所有函数
messageCenter.on("a", funcB);
messageCenter.emit("a", { count: 1 }); // 2
messageCenter.un("a", funcB);
messageCenter.emit("a", { count: 2 });
once=>emit=>emit,once监听一个或多个事件,emit触发事件后立即销毁
messageCenter.once("a", funcB);
messageCenter.emit("a", { count: 1 }); // 2
messageCenter.emit("a", { count: 2 });
on=>clear=>has,on监听不同的事件,clear重置当前事件列表
messageCenter.on("a", funcB);
messageCenter.on("b", funcB);
messageCenter.on("c", funcB);
messageCenter.clear();
console.info(
messageCenter.has("a") || messageCenter.has("b") || messageCenter.has("c")
); // false
console.info(messageCenter.events); // {}
on=>handlerLength,on在同一type中注册多个事件,handlerLength返回事件数量
messageCenter.on("a", funcB);
messageCenter.on("a", funcA);
console.info(messageCenter.handlerLength('a')); // 2
watch=>invoke,watch注册事件,invoke触发事件并等待结果
const funcC = async (args) => {
await syncFn();
return args.reduce((pre, next) => (pre += next));
};
// 异步函数
const syncFn = () => {
return new Promise((res) => {
setTimeout(res, 1000);
});
};
messageCenter.watch("c", funcC);
messageCenter.invoke("c", [1, 2, 3]).then((result) => {
console.log(result); // 6
});
写在最后
以上就是文章的所有内容了,如果对源码有兴趣的同学可以进入下面链接或者用npm,pnpm下载
Gitee:MessageCenter: 基于发布订阅模式实现的一个事件消息中心
NPM:event-message-center - npm
感谢你看到了这里,对文章有任何问题欢迎互相学习讨论,如果文章有帮助到你,请给作者一个点赞作为鼓励吧,你的鼓励是作者创作的动力!