【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…...

Mysql面试总结
基础 1. 数据库的三范式是什么? 第一范式:强调的是列的原子性,即数据库表的每一列都是不可分割的原子数据项。第二范式:要求实体的属性完全依赖于主关键字。所谓完全 依赖是指不能存在仅依赖主关键字一部分的属性。第三范式&…...

【深圳五兴科技】Java后端面经
本文目录 写在前面试题总览1、java集合2、创建线程的方式3、对spring的理解4、Spring Boot 和传统 Spring 框架的一些区别5、springboot如何解决循环依赖6、对mybatis的理解7、缓存三兄弟8、接口响应慢的处理思路9、http的状态码 写在前面 关于这个专栏: 本专栏记录…...

画图(ccf201409-2)解题思路
解题思路 填充100*100二维数组,范围内的元素修改成1,最后累积求和。...

蓝桥杯刷题(一)
一、 import os import sys def dps(s):dp [0] * len(s)dp[0] ord(s[0]) - 96if len(s) 1:return dp[-1]dp[1] max(ord(s[0]) - 96, ord(s[1]) - 96)for i in range(2, len(s)):dp[i] max(dp[i - 1], dp[i - 2] (ord(s[i])) - 96)return dp[-1] s input() print(dps(s))…...

设计模式:策略模式 ⑥
一、策略模式思想 简介 策略模式(Strategy Pattern)属于对象的行为模式。其用意是针对一组算法,将每一个算法封装到具有共同接口的独立的类中,从而使得它们可以相互替换。策略模式使得算法可以在不影响到客户端的情况下发生变化。…...

数据结构从入门到精通——顺序表
顺序表 前言一、线性表二、顺序表2.1概念及结构2.2 接口实现2.3 数组相关面试题2.4 顺序表的问题及思考 三、顺序表具体实现代码顺序表的初始化顺序表的销毁顺序表的打印顺序表的增容顺序表的头部/尾部插入顺序表的头部/尾部删除指定位置之前插入数据和删除指定位置数据顺序表元…...

001-CSS-水平垂直居中布局
水平垂直居中布局 方案一:弹性盒子布局方案二:绝对定位 transform方案三:margin 绝对定位,四个方向为零方案四:绝对定位 margin方案五:绝对定位 calc 方案一:弹性盒子布局 💡 T…...

【[STM32]标准库-自定义BootLoader】
[STM32]标准库-自定义BootLoader BootloaderBootloader的实现BOOTloader工程APP工程 Bootloader bootloader其实就是一段启动程序,它在芯片启动的时候最先被执行,可以用来做一些硬件的初始化或者用作固件热更新,当初始化完成之后跳转到对应的…...

Spring Boot项目中不使用@RequestMapping相关注解,如何动态发布自定义URL路径
一、前言 在Spring Boot项目开发过程中,对于接口API发布URL访问路径,一般都是在类上标识RestController或者Controller注解,然后在方法上标识RequestMapping相关注解,比如:PostMapping、GetMapping注解,通…...

Vue中有哪些优化性能的方法?
Vue是一款流行的JavaScript框架,用于构建交互性强的Web应用程序。在前端开发中,性能优化是一个至关重要的方面,尤其是当应用程序规模变大时。Vue提供了许多优化性能的方法,可以帮助开发人员提升应用程序的性能,从而提升…...