Eswlnk Blog Eswlnk Blog
  • 资源
    • 精彩视频
    • 破解专区
      • WHMCS
      • WordPress主题
      • WordPress插件
    • 其他分享
    • 极惠VPS
    • PDF资源
  • 关于我
    • 论文阅读
    • 关于本站
    • 通知
    • 左邻右舍
    • 玩物志趣
    • 日志
    • 专题
  • 热议话题
    • 游戏资讯
  • 红黑
    • 渗透分析
    • 攻防对抗
    • 代码发布
  • 自主研发
    • 知识库
    • 插件
      • ToolBox
      • HotSpot AI 热点创作
    • 区块
    • 快乐屋
    • 卡密
  • 乱步
    • 文章榜单
    • 热门标签
  • 问答中心反馈
  • 注册
  • 登录
首页 › 代码发布 › 「代码分享」深入理解 JavaScript 的发布订阅模式 | 实现灵活的消息通信与事件管理

「代码分享」深入理解 JavaScript 的发布订阅模式 | 实现灵活的消息通信与事件管理

Eswlnk的头像
Eswlnk
2024-04-24 12:17:49
「代码分享」深入理解 JavaScript 的发布订阅模式 | 实现灵活的消息通信与事件管理-Eswlnk Blog
智能摘要 AI
发布订阅模式(也称观察者模式)用于解耦对象间的依赖,通过事件模型实现异步通信。其核心功能包括订阅、发布、一次性订阅和移除订阅。在JavaScript中,可以通过以下方式避免全局`eventName`冲突: 1. **使用`Symbol`**:创建唯一不重复的事件名,确保不同模块间不会冲突。 2. **命名空间**:通过创建独立的事件实例来隔离不同的事件流。 对于必须先订阅再发布的问题,可以通过消息缓存解决。当订阅者晚于发布者时,缓存机制可以保留已发布的消息,并在订阅时触发通知。例如,RxJS的`ReplaySubject`就实现了这种功能。

前言

发布订阅模式使用上也非常广泛,比如vue的响应式数据就用到了发布订阅模式,前端的原生事件也是发布订阅模式,这种模式主要解决的就是异步的问题,用于代替回调函数,且使得两个对象是松耦合的联系再一起。

「代码分享」深入理解 JavaScript 的发布订阅模式 | 实现灵活的消息通信与事件管理-Eswlnk Blog

发布—订阅模式又叫观察者模式,它定义对象间的一种一对多的依赖关系,当一个对象的状 态发生改变时,所有依赖于它的对象都将得到通知。在 JavaScript 开发中,我们一般用事件模型 来替代传统的发布—订阅模式。

发布订阅实现

var event = {
    eventMap: new Map(),
    on(eventName, callback) {
        let eventList = event.eventMap.get(eventName);
        if (!eventList) {
            eventList = [];
            event.eventMap.set(eventName, eventList);
        }

        eventList.push(callback);
    },
    once(eventName, callback) {
        let eventList = event.eventMap.get(eventName);
        if (!eventList) {
            eventList = [];
            event.eventMap.set(eventName, eventList);
        }

        const onceCallback = function(data) {
            callback(data);
            event.remove(eventName, onceCallback);
        };
        eventList.push(onceCallback);
    },
    emit(eventName, data) {
        const eventList = event.eventMap.get(eventName);
        if (!eventList) return;

        eventList.forEach((callback) => callback(data));
    },
    remove(eventName, callback) {
        const eventList = event.eventMap.get(eventName);
        if (!eventList) return;

        const findIndex = eventList.findIndex((fn) => fn === callback);
        if (findIndex === -1) return;

        eventList.splice(findIndex, 1);
    },
};

event.on("test", (msg) => {
    console.log("获取到消息:", msg);
});

setTimeout(() => {
    event.emit("test", "失败了");
}, 2000);

这是一个比较常见的实现,功能上有订阅,发布,一次性订阅,移除订阅。

命名冲突

当我们全局只用一个event来实现消息订阅通知的时候,随着业务量的增大,eventName总是会有冲突的时候,解决这种冲突有两种比较好的解决办法,一种是使用es6的Symbol定义一个永远不重复的事件名,一种就是使用命名空间,既然你事件名和之前的冲突了,我再创一个全新event来订阅不就行了。

var eventName = Symbol("foo");

使用Symbol后,如果其他地方也要订阅,你就得给对方提供这个Symbol,所以常见的做法就是创建一个变量保存,然后export导出,如果其他地方需要,就引入它。

var Event = (function() {
    const defaultNamespaceName = "_default";
    const eventCache = {};

    function createEvent(namespaceName) {
        if (!namespaceName) namespaceName = defaultNamespaceName;

        if (!eventCache[namespaceName]) {
            eventCache[namespaceName] = {
                eventMap: new Map(),
                on(eventName, callback) {
                    let eventList = this.eventMap.get(eventName);
                    if (!eventList) {
                        eventList = [];
                        this.eventMap.set(eventName, eventList);
                    }

                    eventList.push(callback);
                },
                once(eventName, callback) {
                    let eventList = this.eventMap.get(eventName);
                    if (!eventList) {
                        eventList = [];
                        this.eventMap.set(eventName, eventList);
                    }

                    const onceCallback = (data) => {
                        callback(data);
                        this.remove(eventName, onceCallback);
                    };

                    eventList.push(onceCallback);
                },
                emit(eventName, data) {
                    const eventList = this.eventMap.get(eventName);
                    if (!eventList) return;

                    eventList.forEach((callback) => callback(data));
                },
                remove(eventName, callback) {
                    const eventList = this.eventMap.get(eventName);
                    if (!eventList) return;

                    const findIndex = eventList.findIndex((fn) => fn === callback);
                    if (findIndex === -1) return;

                    eventList.splice(findIndex, 1);
                },
            };
        }

        return eventCache[namespaceName];
    }

    const defaultEvent = createEvent();

    return {
        create: createEvent,
        ...defaultEvent,
    };
})();

Event.on("test", (msg) => {
    console.log("获取到消息:", msg);
});

const namespaceEvent1 = Event.create("namespace1");
const namespaceEvent2 = Event.create("namespace2");

namespaceEvent1.on("test", (msg) => {
    console.log("namespace1获取到消息:", msg);
});

namespaceEvent2.on("test", (msg) => {
    console.log("namespace2获取到消息:", msg);
});

setTimeout(() => {
    Event.emit("test", "失败了");

    namespaceEvent1.emit("test", "成功啦");

    namespaceEvent2.emit("test", "崩溃啦");
}, 2000);

命名空间的方式相对复杂一点,但是也不是很复杂,我们可以通过抛出的create创建命名空间,然后保存这个创建的event对象,然后订阅和通知都使用该对象即可。

必须先订阅再发布吗?

我们目前的代码实现中,都必须是先订阅,再进行发布通知,否则消息通知就会丢失,事实上我们不一定非要订阅后才能发布。

在以下消息队列的设计中,有些消息可能因为异步的原因,它的订阅时机是晚于发布时机的,如果采用上面的方式,就会导致消息的丢失,为了解决这个问题,我们可以实现不必先订阅再发布。

实现这种功能的做法就是消息缓存,我们将发布的消息缓存到内存或者持久化,当用户来订阅的时候,判断缓存中是否存在消息,如果存在就在订阅后,触发一次通知,将缓存的数据传递过去。

至于这个消息你要不要缓存多个,以及缓存多久,是否需要过期,就得配合实际的项目业务来动态调整。

在RxJS的 Replaysubject 就可以实现缓存消息,并在订阅时通知。

假设我们只需要保留最新的一份消息,我们调一下上面代码也可以做到:

var Event = (function() {
    const defaultNamespaceName = "_default";
    const eventCache = {};

    function createEvent(namespaceName) {
        if (!namespaceName) namespaceName = defaultNamespaceName;

        if (!eventCache[namespaceName]) {
            eventCache[namespaceName] = {
                eventMap: new Map(),
                cache: {},
                on(eventName, callback) {
                    let eventList = this.eventMap.get(eventName);
                    if (!eventList) {
                        eventList = [];
                        this.eventMap.set(eventName, eventList);
                    }

                    eventList.push(callback);

                    if (this.cache[eventName]) {
                        callback(this.cache[eventName]);
                    }
                },
                once(eventName, callback) {
                    let eventList = this.eventMap.get(eventName);
                    if (!eventList) {
                        eventList = [];
                        this.eventMap.set(eventName, eventList);
                    }

                    if (this.cache[eventName]) {
                        callback(this.cache[eventName]);
                        return;
                    }

                    const onceCallback = (data) => {
                        callback(data);
                        this.remove(eventName, onceCallback);
                    };
                    eventList.push(onceCallback);
                },
                emit(eventName, data) {
                    const eventList = this.eventMap.get(eventName);
                    if (!eventList) return;

                    eventList.forEach((callback) => callback(data));

                    this.cache[eventName] = data;
                },
                remove(eventName, callback) {
                    const eventList = this.eventMap.get(eventName);
                    if (!eventList) return;

                    const findIndex = eventList.findIndex((fn) => fn === callback);
                    if (findIndex === -1) return;

                    eventList.splice(findIndex, 1);
                },
            };
        }

        return eventCache[namespaceName];
    }

    const defaultEvent = createEvent();

    return {
        create: createEvent,
        ...defaultEvent,
    };
})();
本站默认网盘访问密码:1166
本站默认网盘访问密码:1166
声明:本站原创文章文字版权归本站所有,转载务必注明作者和出处;本站转载文章仅仅代表原作者观点,不代表本站立场,图文版权归原作者所有。如有侵权,请联系我们删除。
JavaScript代码消息
5
0
Eswlnk的头像
Eswlnk
一个有点倒霉的研究牲站长
赞赏
「代码分享」JavaScript 单例模式的实现与应用
上一篇
「技术分享」解密凡科网滑块验证:逆向JS、base64解码与Selenium应用
下一篇

评论 (0)

请登录以参与评论
现在登录
    发表评论

猜你喜欢

  • 小工具开发之EdgeOne免费计划兑换工具
  • 「日志记录」逆向必应翻译网页版API实现免费调用
  • 「渗透分析」如何防范JS注入?nbcio-boot代码审计攻略大公开
  • 「代码分享」第三方平台VIP视频解析API接口
  • 「至臻原创」某系统网站登录功能监测
Eswlnk的头像

Eswlnk

一个有点倒霉的研究牲站长
1108
文章
319
评论
679
获赞

随便看看

JS是什么编程语言
2023-02-25 18:44:36
WordPress开发日志|评论框添加填入QQ自动补全评论者信息功能
2022-02-01 1:12:45
「编程趣事」Python写完一行怎么换下一行
2023-09-22 10:37:17

文章目录

专题展示

WordPress53

工程实践37

热门标签

360 AI API CDN java linux Nginx PDF PHP python SEO Windows WordPress 云服务器 云服务器知识 代码 免费 安全 安卓 工具 开发日志 微信 微软 手机 插件 攻防 攻防对抗 教程 日志 渗透分析 源码 漏洞 电脑 破解 系统 编程 网站优化 网络 网络安全 脚本 苹果 谷歌 软件 运维 逆向
  • 首页
  • 知识库
  • 地图
Copyright © 2023-2025 Eswlnk Blog. Designed by XiaoWu.
本站CDN由 壹盾安全 提供高防CDN安全防护服务
蜀ICP备20002650号-10
页面生成用时 1.402 秒   |  SQL查询 26 次
本站勉强运行:
友情链接: Eswlnk Blog 网站渗透 倦意博客 特资啦!个人资源分享站 祭夜博客 iBAAO壹宝头条
  • WordPress142
  • 网络安全64
  • 漏洞52
  • 软件52
  • 安全48
现在登录
  • 资源
    • 精彩视频
    • 破解专区
      • WHMCS
      • WordPress主题
      • WordPress插件
    • 其他分享
    • 极惠VPS
    • PDF资源
  • 关于我
    • 论文阅读
    • 关于本站
    • 通知
    • 左邻右舍
    • 玩物志趣
    • 日志
    • 专题
  • 热议话题
    • 游戏资讯
  • 红黑
    • 渗透分析
    • 攻防对抗
    • 代码发布
  • 自主研发
    • 知识库
    • 插件
      • ToolBox
      • HotSpot AI 热点创作
    • 区块
    • 快乐屋
    • 卡密
  • 乱步
    • 文章榜单
    • 热门标签
  • 问答中心反馈