JS案例:基于发布订阅实现的事件消息中心-MessageCenter

简介: JS案例:基于发布订阅实现的事件消息中心-MessageCenter

前言

之前写过一篇文章:

JS案例:Observer Pattern(观察者模式)和Publisher-Subscriber Pattern(发布者/订阅者模式)_DieHunter1024的博客-CSDN博客

发布/订阅模式和观察者模式一样吗?在许多地方我们都能见到基于这二者或者说基于某种设计模式的框架,函数或插件在浏览器中使用addEventListener(type,fn)对dom元素进行事件委托,事件监听用户的异步操作Android中也有一个事件发布/订阅的轻量级框架:EventBus,原理与web相似Socket.io的许多方法也是基于此类模式,监听与触发事件,批量广播等在Node中同样也有一个events事件触发器解决异步操作的同步响应

https://hunter1024.blog.csdn.net/article/details/113770950


其中简单描述了一下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


源码参考:message-center.js - npm


相关文章
|
1月前
|
Web App开发 JavaScript 前端开发
javascript onkeydown事件
javascript onkeydown事件
|
3月前
|
JavaScript 前端开发
js事件队列
js事件队列
141 55
|
1月前
|
JavaScript
js两种移除事件的方法
js两种移除事件的方法
30 2
|
2月前
|
JavaScript 前端开发
JavaScript 事件
JavaScript 事件
34 2
|
3月前
|
数据采集 Web App开发 JavaScript
利用Selenium和XPath抓取JavaScript动态加载内容的实践案例
利用Selenium和XPath抓取JavaScript动态加载内容的实践案例
|
25天前
|
存储 JavaScript 前端开发
js事件队列
【10月更文挑战第15天】
43 6
|
2月前
|
JavaScript 前端开发
JavaScript HTML DOM 事件
JavaScript HTML DOM 事件
22 5
|
2月前
|
监控 JavaScript 前端开发
|
28天前
|
JavaScript API
深入解析JS中的visibilitychange事件:监听浏览器标签间切换的利器
深入解析JS中的visibilitychange事件:监听浏览器标签间切换的利器
79 0
|
1月前
|
JavaScript
js两种移除事件的方法
js两种移除事件的方法
51 0