【Javascript】设计模式之发布订阅模式
文章目录
- 1、现实中的发布-订阅模式
- 2、DOM 事件
- 3、简单的发布-订阅模式
- 4、通用的发布-订阅模式
- 5、先发布再订阅
- 6、小结
发布—订阅模式又叫观察者模式,它定义对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都将得到通知。在 JavaScript 开发中,我们一般用事件模型来替代传统的发布—订阅模式
1、现实中的发布-订阅模式
小明最近看上了一套房子,到了售楼处之后才被告知,该楼盘的房子早已售罄。好在售楼
处告诉小明,不久后还有一些尾盘推出。于是小明离开之前,把电话号码留在了售楼处,相同的还有小红,小强。于是新楼盘推出的时候,售楼处会翻开花名册,遍历上面的电话号码,依次发送一条短信来通知他们
2、DOM 事件
只要我们曾经在 DOM 节点上面绑定过事件函数,那我们就曾经使用过发布—订阅模式
document.body.addEventListener( 'click', function(){ alert(2);
}, false ); document.body.click(); // 模拟用户点击
3、简单的发布-订阅模式
发布-订阅模式的实现步骤
1、定义发布者
2、给发布者添加一个缓存列表,用于存放回调函数以便通知订阅者
3、最后发布消息的时候,发布者会遍历这个缓存列表,依次触发里面存放的订阅者回调函数
代码示例:
var salesOffices = {}; // 定义发布者
salesOffices.clientList = []; // 缓存列表,存放订阅者的回调函数
salesOffices.listen = function (fn) {// 增加订阅者this.clientList.push(fn); // 订阅的消息添加进缓存列表
};
salesOffices.trigger = function () {// 发布消息for (var i = 0; i < this.clientList.length; i++) {var fn = this.clientList.length;fn.apply(this, arguments); // arguments 是发布消息时带上的参数}
};
测试:
salesOffices.listen(function (price, squareMeter) {// 小明订阅消息console.log('小明价格= ' + price);console.log('小明squareMeter= ' + squareMeter);
});
salesOffices.listen(function (price, squareMeter) {// 小红订阅消息console.log('小红价格= ' + price);console.log('小红squareMeter= ' + squareMeter);
});salesOffices.trigger(2000, 300);
salesOffices.trigger(2000, 700);
问题:
订阅者接收到了发布者发布的每个消息,有些并不是订阅者需要的
解决:
要增加一个标示 key,让订阅者只订阅自己感兴趣的消息:
改写代码:
var salesOffices = {}; // 定义发布者
salesOffices.clientList = {}; // 缓存对象,存放订阅者的回调函数
salesOffices.listen = function (key, fn) {if (!this.clientList[key]) {// 如果还没有订阅过此类消息,给该类消息创建一个缓存列表this.clientList[key] = [];}this.clientList[key].push(fn); // 订阅的消息添加进消息缓存列表
};
salesOffices.trigger = function () {// 发布消息var key = Array.prototype.shift.call(arguments); // 取出消息类型var fns = this.clientList[key]; // 取出该消息对应的回调函数集合if (!fns || fns.length === 0) {// 如果没有订阅该消息,则返回return false;}for (var i = 0; i < fns.length; i++) {var fn = fns[i];fn.apply(this, arguments); // (2) // arguments 是发布消息时附送的参数}
};
测试:
salesOffices.listen('squareMeter88', function (price) {// 小明订阅 88 平方米房子的消息console.log('价格= ' + price); // 输出: 2000000
});
salesOffices.listen('squareMeter110', function (price) {// 小红订阅 110 平方米房子的消息console.log('价格= ' + price); // 输出: 3000000
});salesOffices.trigger('squareMeter88', 30000);
salesOffices.trigger('squareMeter110', 70000);
4、通用的发布-订阅模式
包含:发布-订阅,取消订阅
var Event = {clientList: {},listen: function (key, fn) {if (!this.clientList[key]) {this.clientList[key] = [];}this.clientList[key].push(fn);},trigger: function () {var key = Array.prototype.shift.call(arguments);var fns = this.clientList[key];if (!fns || fns.length === 0) {return false;}for (var i = 0, fn; (fn = fns[i++]); ) {fn.apply(this, arguments);}},// 增加 remove 方法remove(key, fn) {var fns = this.clientList[key];if (!fns) {return false;}if (!fn) {fns && (fns.length = 0);} else {for (var i = fns.length - 1; i >= 0; i--) {var _fn = fns[i];if (fn === _fn) {fns.splice(i, 1);}}}},
};
测试:
var f1 = function (price) {console.log('价格= ' + price);
};
Event.listen('s88', f1);var f2 = function (price) {console.log('价格= ' + price);
};
Event.listen('s110', f2);Event.remove('s110', f2); // 删除订阅Event.trigger('s88', 30000);
Event.trigger('s110', 70000);
5、先发布再订阅
应用场景:发布者发布的内容,不管订阅者在发布之前订阅,或者发布之后订阅,都可触发订阅者订阅的内容
代码:
var Event = (function () {var clientList = {};var offlineStack = {}; // 离线缓存参数var triggerStack = {}; // 已触发trigger的参数缓存var listen;var trigger;var remove;listen = function (key, fn) {if (!clientList[key]) {clientList[key] = [];}clientList[key].push(fn);// 如果此时订阅的事件,已经发布了,则自定触发一次订阅内容(fn)if (triggerStack[key]) {fn.apply(this, triggerStack[key]);} else if (offlineStack[key]) {// 如果是离线状态,则触发事件fn.apply(this, offlineStack[key]);}};trigger = function () {var key = Array.prototype.shift.call(arguments);var fns = clientList[key];if (fns) {// 已经有人订阅此事件,将参数缓存//(假如有些订阅者比较晚订阅,且发布者已经发布过了,那么这个订阅者订阅的时候,自动触发一次订阅内容)triggerStack[key] = [...arguments];for (var i = 0; i < fns.length; i++) {fns[i].apply(this, arguments);}} else {// 表示当前还没有人订阅此事件,则先将参数缓存起来offlineStack[key] = [...arguments];}};// 取消订阅remove = function (key, fn) {var fns = this.clientList[key];if (!fns) {return false;}if (!fn) {// 如果没有传入具体的回调函数,表示需要取消 key 对应消息的所有订阅fns && (fns.length = 0);} else {for (var l = fns.length - 1; l >= 0; l--) {var _fn = fns[l];if (_fn === fn) {fns.splice(l, 1);}}}};return {listen: listen,trigger: trigger,remove: remove,};
})();
测试1:先订阅,再发布
// 先订阅
Event.listen('test1', function (a) {console.log('我是发布之前的订阅者1:', a);
});
Event.listen('test1', function (a) {console.log('我是发布之前的订阅者2:', a);
});
// 再发布
Event.trigger('test1', 12);// 我是发布之前的订阅者1: 12
// 我是发布之前的订阅者2: 12
测试2:先发布,再订阅
// 先发布
Event.trigger('test1', 12);// 再订阅
Event.listen('test1', function (a) {console.log('我是发布之后的订阅者1:', a);
});
Event.listen('test1', function (a) {console.log('我是发布之后的订阅者2:', a);
});// 我是发布之后的订阅者1: 12
// 我是发布之后的订阅者2: 12
测试3:先订阅,再发布,再订阅
// 先订阅
Event.listen('lis1', function (a) {console.log('我是发布之前的订阅者1:', a);
})
Event.listen('lis1', function (a) {console.log('我是发布之前的订阅者2:', a);
})// 再发布
console.log('---第1次发布');
Event.trigger('lis1', 123);
console.log('---第1次发布完成');// 再订阅
Event.listen('lis1', function (b) {console.log('我是发布之后的订阅者~:', b);
})// ---第1次发布
// 我是发布之前的订阅者1: 123
// 我是发布之前的订阅者2: 123
// ---第1次发布完成
// 我是发布之后的订阅者~: 123
测试4:先发布,再订阅,再发布,再订阅
// 先发布
console.log('------第1次发布-------');
Event.trigger('lis1', 123);// 再订阅
Event.listen('lis1', function (a) {console.log('我是发布之后的订阅者1:', a);
})
Event.listen('lis1', function (a) {console.log('我是发布之后的订阅者2:', a);
})// 再发布
console.log('------第2次发布-------');
Event.trigger('lis1', 456);// 再订阅
Event.listen('lis1', function (a) {console.log('我是发布之后的再次订阅者1:', a);
})
Event.listen('lis1', function (a) {console.log('我是发布之后的再次订阅者2:', a);
})// ------第1次发布-------
// 我是发布之后的订阅者1: 123
// 我是发布之后的订阅者2: 123// ------第2次发布-------
// 我是发布之后的订阅者1: 456
// 我是发布之后的订阅者2: 456
// 我是发布z之后的再次订阅者1: 456
// 我是发布z之后的再次订阅者2: 456
测试5:先订阅,再发布,再订阅,再发布
Event.listen('lis1', function (a) {console.log('我是发布之前的订阅者1:', a);
})
Event.listen('lis1', function (a) {console.log('我是发布之前的订阅者2:', a);
})console.log('---第1次发布');
Event.trigger('lis1', 123);
console.log('---第1次发布完成');Event.listen('lis1', function (b) {console.log('我是发布之后的订阅者~:', b);
})console.log('---第2次发布');
Event.trigger('lis1', 456);
console.log('---第2次发布完成');// ---第1次发布
// 我是发布之前的订阅者1: 123
// 我是发布之前的订阅者2: 123
// ---第1次发布完成
// 我是发布之后的订阅者~: 123// ---第2次发布
// 我是发布之前的订阅者1: 456
// 我是发布之前的订阅者2: 456
// 我是发布之后的订阅者~: 456
// ---第2次发布完成
6、小结
优点:
一为时间上的解耦,二为对象之间的解耦
缺点:
1、创建订阅者本身要消耗一定的时间和内存,而且当你订阅一个消息后,也许此消息最后都未发生,但这个订阅者会始终存在于内存中
2、如果过度使用的话,对象和对象之间的必要联系也将被深埋在背后,会导致程序难以跟踪维护和理解
应用:
应用非常广泛,既可以用在异步编程中,也可以帮助我们完成更松耦合的代码编写
相关文章:
【Javascript】设计模式之发布订阅模式
文章目录 1、现实中的发布-订阅模式2、DOM 事件3、简单的发布-订阅模式4、通用的发布-订阅模式5、先发布再订阅6、小结 发布—订阅模式又叫观察者模式,它定义对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于…...
DataLoader
import torchvision from torch.utils.data import DataLoader from torch.utils.tensorboard import SummaryWriter# 准备的测试数据集 数据放在了CIFAR10文件夹下test_data torchvision.datasets.CIFAR10("./CIFAR10",trainFalse, transformtorchvision.transfor…...
持续集成(CICD)- Jenkins+Git+gogs综合实战(笔记二)
文章目录 七、拉取代码方法一:直接填写命令进行拉取(不建议用这种)方法二:使用源码管理拉取代码步骤一:确认环境(检查自己是否有Git插件)步骤二:构建项目时对项目的源码管理选择 Git步骤三:输入你仓库的SSH地址或者https地址,并且添加gitee的用户名和密方法一和方法二…...
VUE:key属性的作用
在 Vue.js 中,key属性的主要作用是帮助 Vue 在进行 DOM 更新时,能够更准确地识别哪些节点可以复用。 当key值发生变化时,Vue 会执行以下步骤: 1.查找旧节点:Vue 会查找虚拟 DOM 中具有旧key值的节点。 2.匹配新节点…...
linux的通信方案(SYSTEM V)
文章目录 共享内存(Share Memory)信号队列(Message Queue)信号量(semaphore) 进程间通信的核心理念:让不同的进程看见同一块资源 linux下的通信方案: SYSTEM V 共享内存(Share Memory) 特点:1.共享内存是进程见通信最…...
VUE 入门及应用 ( 路由 router )
6.前端路由 router Vue Router | Vue.js 的官方路由 (vuejs.org) 官方地址 : https://router.vuejs.org/zh/ 6.1.基本配置 6.1.0.准备 MyPage.vue 创建 用于测试 vue文件 ../views/MyPage.vue <template><div><h1>MyPage</h1></div> </…...
SpringBoot集成RocketMQ
RocketMQ是一个纯Java、分布式、队列模型的开源消息中间件,前身是MetaQ,是阿里参考Kafka特点研发的一个队列模型的消息中间件,后开源给apache基金会成为了apache的顶级开源项目,具有高性能、高可靠、高实时、分布式特点。 环境搭…...
【Web】关于FastJson反序列化开始前的那些前置知识
目录 FastJson介绍 FJ序列化与反序列化方法 关于反序列化三种方式的关系与区别 FastJson反序列化漏洞原理通识 关于getter&setter FastJson介绍 FastJson(快速JSON)是一个Java语言编写的高性能、功能丰富且易于使用的JSON解析和序列化库。它由…...
工业镜头的重要参数之视场、放大倍率、芯片尺寸--51camera
今天来简单介绍下工业镜头中常用的参数中的三个: 1、视场 视场(FOV)也称视野,是指能被视觉系统观察到的物方可视范围。 对于镜头而言,可观察到的视场跟镜头放大倍率及相机芯片选择有关。因此需要根据被观察物体的尺寸ÿ…...
基于java springboot+redis网上水果超市商城设计和实现以及文档
基于java springbootredis网上水果超市商城设计和实现以及文档 博主介绍:多年java开发经验,专注Java开发、定制、远程、文档编写指导等,csdn特邀作者、专注于Java技术领域 作者主页 央顺技术团队 Java毕设项目精品实战案例《1000套》 欢迎点赞 收藏 ⭐留…...
3. 在Go语言项目中使用Zap日志库
文章目录 一、介绍二、 默认的Go Logger1. 实现Go Logger2. 设置Logger3. 使用Logger4. Logger的运行5. Go Logger的优势和劣势 三、Uber-go Zap1. 为什么选择Uber-go zap2. 安装3. 配置Zap Logger4. 定制logger4.1 将日志写入文件而不是终端4.2 将JSON Encoder更改为普通的Log…...
想要节省成本,哪个品牌的https证书值得考虑?
为了确保网站数据传输安全,启用HTTPS加密是关键步骤。在众多SSL证书供应商中,如何找到价格合理且品质优良的HTTPS加密证书呢?本文将探讨这个问题,并重点关注具有高性价比优势的沃通CA。 沃通CA作为业内知名的SSL证书服务商&#x…...
R语言及其开发环境简介
R语言及其开发环境简介 R 语言历史 R 语言来自 S 语言,是 S 语言的一个变种。S语言由贝尔实验室研究开发,著名的 C 语言、Unix 系统也是贝尔实验室开发的。R 属于 GNU 开源软件,最初发布于1997年,实现了与 S 语言基本相同的功能…...
部署DNS解析服务
一、安装软件,关闭防火墙,启动服务 1.yum install -y bind bind-utils bind-chroot 2.systemctl stop firewalld && setenforce 0 3.systemctl start named 二、工作目录 /var/named/chroot/etc #存放主配置文件 /var/named/chroot/var/n…...
2024新算法:鹅算法优化VMD参数,五种适应度函数任意切换,最小包络熵、样本熵、信息熵、排列熵、排列熵/互信息熵...
本期采用鹅算法优化一下VMD参数。利用MATLAB官方自带的VMD函数。 替换为官方自带的VMD函数后,寻优速度真的大幅度提升!数据量大的不妨都试试这个官方的VMD函数。当然要下载2020a以上的MATLAB才可以哦! 同样以西储大学数据集为例,选…...
自定义注解校验
在日常开发中经常会用到String类型的数据当作数值进行映射,势必会做出数值范围的校验,可以通过自定义注解的办法简化代码实现,减少冗余代码。 Target({ElementType.FIELD}) Retention(RetentionPolicy.RUNTIME) Constraint(validatedBy St…...
由数据范围反推算法复杂度以及算法内容
一般ACM或者笔试题的时间限制是1秒或2秒。 在这种情况下,C代码中的操作次数控制在 1 0 7 ∼ 1 0 8 10^7\sim10^8 107∼108为最佳。 下面给出在不同数据范围下,代码的时间复杂度和算法该如何选择: n ≤ 30 n\leq30 n≤30,指数级别…...
js监听F11触发全屏事件
当用户使用 F11 键进行浏览器全屏时,由于此时并非通过浏览器提供的 Fullscreen API 进入全屏模式,因此无法通过 fullscreenchange 事件来监听全屏状态的变化。在这种情况下,可以通过监听 resize 事件来检测浏览器窗口大小的变化,从…...
Seata 2.x 系列【1】专栏导读
有道无术,术尚可求,有术无道,止于术。 本系列Spring Boot 版本 3.1.0 本系列Seata 版本 2.0.0 源码地址:https://gitee.com/pearl-organization/study-seata-demo 文章目录 1. 背景2. 简介3. 适用人群4. 环境及版本5. 文章导航5…...
fly-barrage 前端弹幕库(3):滚动弹幕的设计与实现
项目官网地址:https://fly-barrage.netlify.app/; 👑🐋🎉如果感觉项目还不错的话,还请点下 star 🌟🌟🌟。 Gitee:https://gitee.com/fei_fei27/fly-barrage&a…...
Flutter文件操作实战:File_selector跨平台文件处理从入门到精通
1. 为什么Flutter开发者都需要掌握File_selector? 在移动应用和桌面应用开发中,文件操作就像我们日常生活中的"文件柜"——你需要存放、查找、整理各种文档。而Flutter作为跨平台框架,最大的挑战就是如何在不同操作系统上实现统一的…...
如何免费构建个人游戏串流服务器:Sunshine开源方案完整指南
如何免费构建个人游戏串流服务器:Sunshine开源方案完整指南 【免费下载链接】Sunshine Self-hosted game stream host for Moonlight. 项目地址: https://gitcode.com/GitHub_Trending/su/Sunshine Sunshine是一款开源的自托管游戏串流服务器,让您…...
GitHub资源精准下载:DownGit实现90%带宽节省的技术方案
GitHub资源精准下载:DownGit实现90%带宽节省的技术方案 【免费下载链接】DownGit github 资源打包下载工具 项目地址: https://gitcode.com/gh_mirrors/dow/DownGit 在开源开发流程中,开发者经常需要从GitHub仓库获取特定文件夹资源。传统方式下&…...
告别手速焦虑:Python大麦网自动抢票脚本终极指南
告别手速焦虑:Python大麦网自动抢票脚本终极指南 【免费下载链接】Automatic_ticket_purchase 大麦网抢票脚本 项目地址: https://gitcode.com/GitHub_Trending/au/Automatic_ticket_purchase 还在为心仪演出门票秒光而烦恼吗?每次热门演唱会开票…...
Krita AI Diffusion IP-Adapter功能异常深度排查与解决方案
Krita AI Diffusion IP-Adapter功能异常深度排查与解决方案 【免费下载链接】krita-ai-diffusion Streamlined interface for generating images with AI in Krita. Inpaint and outpaint with optional text prompt, no tweaking required. 项目地址: https://gitcode.com/g…...
ContextMenuManager:让Windows交互回归高效本质
ContextMenuManager:让Windows交互回归高效本质 【免费下载链接】ContextMenuManager 🖱️ 纯粹的Windows右键菜单管理程序 项目地址: https://gitcode.com/gh_mirrors/co/ContextMenuManager 当你在Windows系统中右键点击文件时,是否…...
实测Qwen3-4B:256K超长上下文,处理长文档、写长文真实案例
实测Qwen3-4B:256K超长上下文,处理长文档、写长文真实案例 1. 引言:为什么关注长上下文能力 在日常工作和创作中,我们经常遇到需要处理超长文档的场景:分析上百页的PDF报告、阅读整本电子书、编写长篇技术文档等。传…...
【数据结构】数组与特殊矩阵
数据结构的学习中,数组与特殊矩阵是基础且核心的内容。它们不仅是程序设计中最常用的线性结构,更是处理复杂矩阵运算的基础。本文将结合解析与真题,带你彻底搞懂数组的存储方式和特殊矩阵的压缩存储技巧。一、一维数组与二维数组:…...
告别手动重复!用Python+ArcPy实现多要素批量裁剪年度影像的保姆级教程
PythonArcPy自动化遥感影像裁剪:从原理到实战的完整解决方案 遥感影像处理是GIS工程师的日常必修课。每当拿到新一年的土地利用数据或行政区划影像时,最头疼的莫过于要为每个行政单元单独裁剪每年的数据。我曾花费整整一周时间手动处理30个乡镇5年的NDVI…...
从原理到代码:深入解析UniFormer的多头关系聚合器(MHRA)设计
从原理到代码:深入解析UniFormer的多头关系聚合器(MHRA)设计 视频理解领域近年来经历了从3D卷积网络到视觉Transformer的范式转变,但两者在时空特征提取上各有限制。3D CNN擅长捕捉局部时空特征却受限于固定感受野,而视觉Transformer虽能建模…...
