JavaScript 简单实现观察者模式和发布-订阅模式 秒速五厘米 2023-10-14 08:41 33阅读 0赞 #### JavaScript 简单实现观察者模式和发布-订阅模式 #### * 1. 观察者模式 * * 1.1 什么是观察者模式 * 1.2 代码实现 * 2. 发布-订阅模式 * * 2.1 什么是发布-订阅模式 * 2.2 代码实现 * * 2.2.1 基础版 * 2.2.2 取消订阅 * 2.2.3 订阅一次 ## 1. 观察者模式 ## ### 1.1 什么是观察者模式 ### 概念:观察者模式定义对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都将得到通知。 **如何理解这句话呢?来举个生活中的例子** 学生小明情绪比较容易波动,所以当小明的情绪发生变化时,父母和老师希望及时获得通知,以便可以采取适当的措施来帮助他。 * 首先家长和老师(观察者)都会告诉小明他们对他的情绪状态很关注。(订阅事件) * 当小明(被观察者)的情绪发生变化时,他会通知所有注册过的观察者。例如,如果小明感到很开心,他会告诉父母和老师:“我今天心情很好!”;如果他感到沮丧,他也会告诉父母和老师:“我今天感觉不太好。”(通知变化) 这样父母和老师就能及时了解小明的情绪状态,当小明情绪低落时,他们可以给予他关心、安慰和支持。 在这个例子中,小明就是被观察者,而父母和老师都是观察者。 ### 1.2 代码实现 ### **观察者模式有何应用呢?** Vue的响应式就是基于观察者模式的,下面就来简单实现一下它的代码。 // 被观察者 学生 class Subject { constructor() { this.state = "happy"; this.observers = []; // 存储所有的观察者 } //新增观察者 add(o) { this.observers.push(o); } //获取状态 getState() { return this.state; } // 更新状态并通知 setState(newState) { this.state = newState; this.notify(); } //通知所有的观察者 notify() { this.observers.forEach((o) => o.update(this)); } } // 观察者 父母和老师 class Observer { constructor(name) { this.name = name; } //更新 update(student) { console.log(`亲爱的${ this.name} 通知您当前学生的状态是${ student.getState()}`); } } let student = new Subject(); let parent = new Observer("父母"); let teacher = new Observer("老师"); //添加观察者 student.add(parent); student.add(teacher); //设置被观察者的状态 student.setState("sad"); ![在这里插入图片描述][c9be2082f04c4dbfa3e6858507082f4f.png] ## 2. 发布-订阅模式 ## ### 2.1 什么是发布-订阅模式 ### 发布订阅模式跟观察者模式很像,它们其实都有**发布者**和**订阅者**,但是他们是有区别的: * 观察者模式的发布和订阅是互相依赖的 * 发布订阅模式的发布和订阅是不互相依赖的,因为有一个**统一调度中心** 为了更好区分这两种设计模式,接着上述例子。 * 所有老师都希望订阅小明的情绪状态,他们向情绪监测系统注册自己,来时刻关注小明的情绪。(向调度中心订阅事件) * 当小明的情绪发生变化时,情绪监测系统会将消息发布给所有订阅了小明情绪状态的老师。例如,如果小明在上课时感到烦躁,情绪监测系统会发布消息给老师:“小明情绪不稳定,请关注他的情绪变化。”(调度中心通知变化) 通过发布订阅模式,小明不需要直接告诉每位老师他的情绪状态,而是通过情绪监测系统自动发布消息给所有订阅了他情绪状态的老师。这种发布者**不直接接触**到订阅者的模式,就是发布订阅模式。 ![在这里插入图片描述][4b4c3cd244394b96b0e185be31c25112.png] **那么发布订阅模式有何应用呢?** Vue的EventBus事件总线其实就是用了发布订阅模式。用法如下: 1.创建全局事件总线 // main.js import Vue from "vue" Vue.prototype.$bus = new Vue() 2.通过on订阅事件 //组件A export default{ mounted(){ // 监听事件的触发 this.$bus.$on("sendMsg", data => { console.log(data)//身体健康 }) }, beforeDestroy(){ // 取消监听 this.$bus.$off("sendMsg") } } 3.通过emit发布事件 //组件B <template> <button @click="handlerClick">点击发送数据</button> </template> export default{ methods:{ handlerClick(){ this.$bus.$emit("sendMsg", "身体健康") } } } 了解了EventBus的使用后,那么接下来就来手动实现一个EventBus。 ### 2.2 代码实现 ### #### 2.2.1 基础版 #### 实现目标:使用 $on 订阅事件,使用 $emit 发布事件。 主要思路: * 创建一个缓存列表对象,存放订阅的事件名和回调 * on 方法用来把回调函数都加到缓存列表中(订阅者注册事件到调度中心) * emit方法根据事件名去逐个执行对应缓存列表中的函数(发布者发布事件到调度中心) class EventBus { constructor() { // 缓存列表,用来存放注册的事件与回调 this.cache = { }; } // 订阅事件 on(name, cb) { // 如果当前事件没有订阅过,就给事件创建一个队列 if (!this.cache[name]) { this.cache[name] = []; //由于一个事件可能注册多个回调函数,所以使用数组来存储事件队列 } this.cache[name].push(cb); } // 触发事件 emit(name, ...args) { // 检查目标事件是否有监听函数队列 if (this.cache[name]) { // 逐个调用队列里的回调函数 this.cache[name].forEach((callback) => { callback(...args); }); } } } // 测试 let eventBus = new EventBus(); // 订阅事件 eventBus.on("teacherName1", (pos, state) => { console.log(`订阅者小陈老师,小明同学当前在${ pos},心情状态是${ state}`); }); eventBus.on("teacherName1", (pos, state) => { console.log(`订阅者小陈老师,小明同学当前在${ pos},心情状态是${ state}`); }); eventBus.on("teacherName2", (pos, state) => { console.log(`订阅者小李老师,小明同学当前在${ pos},心情状态是${ state}`); }); // 发布事件 eventBus.emit("teacherName1", "教室", "伤心"); eventBus.emit("teacherName2", "操场", "开心"); 输出结果: ![在这里插入图片描述][bb73c88e695b485a861a3744dc9edf30.png] #### 2.2.2 取消订阅 #### 实现目标:增加 off 方法取消订阅。 * off 方法:找到当前取消事件名对应的函数队列中相应回调,进行删除 class EventBus { constructor() { // 缓存列表,用来存放注册的事件与回调 this.cache = { }; } // 订阅事件 on(name, cb) { // 如果当前事件没有订阅过,就给事件创建一个队列 if (!this.cache[name]) { this.cache[name] = []; //由于一个事件可能注册多个回调函数,所以使用数组来存储事件队列 } this.cache[name].push(cb); } // 触发事件 emit(name, ...args) { // 检查目标事件是否有监听函数队列 if (this.cache[name]) { // 逐个调用队列里的回调函数 this.cache[name].forEach((callback) => { callback(...args); }); } } // 取消订阅 off(name, cb) { const callbacks = this.cache[name]; const index = callbacks.indexOf(cb); if (index !== -1) { callbacks.splice(index, 1); } } } // 测试 let eventBus = new EventBus(); let event1 = function (...args) { console.log(`通知1-订阅者小陈老师,小明同学当前心情状态:${ args}`) }; let event2 = function (...args) { console.log(`通知2-订阅者小陈老师,小明同学当前心情状态:${ args}`) }; // 订阅事件 eventBus.on("teacherName1", event1); eventBus.on("teacherName1", event2); // 取消订阅事件1 eventBus.off('teacherName1', event1); // 发布事件 eventBus.emit("teacherName1", "教室", "上课", "打架", "愤怒"); eventBus.emit("teacherName2", "教室", "上课", "打架", "愤怒"); 输出结果: ![在这里插入图片描述][9ec82f93e41c49aeaec1db21b7d369b0.png] #### 2.2.3 订阅一次 #### 实现目标:增加 once 方法只订阅一次。 * once 方法只监听一次,执行完第一次回调函数后,自动删除当前订阅事件 class EventBus { constructor() { // 缓存列表,用来存放注册的事件与回调 this.cache = { }; } // 订阅事件 on(name, cb) { // 如果当前事件没有订阅过,就给事件创建一个队列 if (!this.cache[name]) { this.cache[name] = []; //由于一个事件可能注册多个回调函数,所以使用数组来存储事件队列 } this.cache[name].push(cb); } // 触发事件 emit(name, ...args) { // 检查目标事件是否有监听函数队列 if (this.cache[name]) { // 逐个调用队列里的回调函数 this.cache[name].forEach((callback) => { callback(...args); }); } } // 取消订阅 off(name, cb) { const callbacks = this.cache[name]; const index = callbacks.indexOf(cb); if (index !== -1) { callbacks.splice(index, 1); } } // 只订阅一次 once(name, cb) { // 执行完第一次回调函数后,自动删除当前订阅事件 const fn = (...args) => { cb(...args); this.off(name, fn); }; this.on(name, fn); } } // 测试 let eventBus = new EventBus(); let event1 = function (...args) { console.log(`通知1-订阅者小陈老师,小明同学当前心情状态:${ args}`) }; // 订阅事件,只订阅一次 eventBus.once("teacherName1", event1); // 发布事件 eventBus.emit("teacherName1", "教室", "上课", "打架", "愤怒"); eventBus.emit("teacherName1", "教室", "上课", "打架", "愤怒"); eventBus.emit("teacherName1", "教室", "上课", "打架", "愤怒"); 输出结果: ![在这里插入图片描述][c7f2593f0a304c75a2c2325b3cbf192f.png] **写作不易,你的一赞一评,就是我前行的最大动力。如有问题,欢迎指出!** [c9be2082f04c4dbfa3e6858507082f4f.png]: https://img-blog.csdnimg.cn/c9be2082f04c4dbfa3e6858507082f4f.png [4b4c3cd244394b96b0e185be31c25112.png]: https://img-blog.csdnimg.cn/4b4c3cd244394b96b0e185be31c25112.png [bb73c88e695b485a861a3744dc9edf30.png]: https://img-blog.csdnimg.cn/bb73c88e695b485a861a3744dc9edf30.png [9ec82f93e41c49aeaec1db21b7d369b0.png]: https://img-blog.csdnimg.cn/9ec82f93e41c49aeaec1db21b7d369b0.png [c7f2593f0a304c75a2c2325b3cbf192f.png]: https://img-blog.csdnimg.cn/c7f2593f0a304c75a2c2325b3cbf192f.png
相关 JavaScript 简单实现观察者模式和发布-订阅模式 JavaScript 简单实现观察者模式和发布-订阅模式 1. 观察者模式 1.1 什么是观察者模式 1.2 代码实现 2. 发布- 秒速五厘米/ 2023年10月14日 08:41/ 0 赞/ 34 阅读
相关 观察者模式 vs 发布订阅模式 目录 场景 观察者模式 发布订阅模式 总结 -------------------- 场景 有一回面试,面试官问: 末蓝、/ 2023年10月06日 19:03/ 0 赞/ 3 阅读
相关 【JavaScript 设计模式】观察者模式与发布订阅模式 JavaScript 设计模式系列文章: [设计模式总览][Link 1] [工厂模式][Link 2] [单例模式][Link 3] [观察者模式/ Bertha 。/ 2022年12月04日 07:58/ 0 赞/ 231 阅读
相关 订阅发布和观察者模式 发布订阅模式 > 把多个方法暂存起来,最后一次触发执行 作用: 解偶 使用场景: 如,多个类或者函数内,可以分散订阅某个操作,最后统一发布。分散的好处就是不 ╰+攻爆jí腚メ/ 2022年08月28日 14:47/ 0 赞/ 209 阅读
相关 浅谈JavaScript设计模式——观察者模式(发布订阅模式) 观察者模式,又称为发布订阅模式,它定义了一种一对多的关系,让多个观察者对象同时监听某一个主题对象,这个主题对象的状态发生变化时就会通知所有的观察者对象,使得它们能够自动更新自己 分手后的思念是犯贱/ 2022年06月18日 08:51/ 0 赞/ 253 阅读
相关 观察者模式(发布-订阅者模式) 观察者模式定义了一种依赖关系,解决了主体对象和观察者之间功能的耦合,主要应用于大型项目的模块化开发中,解决团队开发中模块之间的通信问题,利用观察者模式还可以实现自定义事件。 素颜马尾好姑娘i/ 2022年05月22日 06:00/ 0 赞/ 217 阅读
相关 javascript 观察者模式 发布订阅模式 观察者模式 观察者模式,每一个观察者对象有两个方法 添加监听`subscribe` 发布事件`publish` 观察者有个`list`存放所有的已经添加监 本是古典 何须时尚/ 2022年04月24日 10:14/ 0 赞/ 213 阅读
相关 JavaScript中观察者和发布订阅模式 可以参考文章: [https://juejin.im/post/5a14e9edf265da4312808d86][https_juejin.im_post_5a14e9e 超、凢脫俗/ 2022年01月27日 07:07/ 0 赞/ 224 阅读
相关 发布订阅模式(观察者模式) 设计模式的目的就是使类成为可复用的组件。 在观察者模式中观察者接口只注重被观察者,而被观察者接口只注重观察者,具体是观察者接口实现类中的哪一个并不在意,而被观察者也是如此。这 清疚/ 2021年12月15日 00:27/ 0 赞/ 293 阅读
还没有评论,来说两句吧...