JavaScript中的发布订阅和观察者模式:如何优雅地处理事件和数据更新

本文主要是介绍JavaScript中的发布订阅和观察者模式:如何优雅地处理事件和数据更新,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

​🌈个人主页:前端青山
🔥系列专栏:JavaScript篇
🔖人终将被年少不可得之物困其一生

依旧青山,本期给大家带来JavaScript篇专栏内容:JavaScript-订阅观察者模式

目录

说说你对发布订阅、观察者模式的理解?区别?

一、观察者模式

二、发布订阅模式

三、区别

设计模式

单例模式

发布订阅模式

案例

一、观察者模式

观察者模式定义了对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都将得到通知,并自动更新

观察者模式属于行为型模式,行为型模式关注的是对象之间的通讯,观察者模式就是观察者和被观察者之间的通讯

例如生活中,我们可以用报纸期刊的订阅来形象的说明,当你订阅了一份报纸,每天都会有一份最新的报纸送到你手上,有多少人订阅报纸,报社就会发多少份报纸

报社和订报纸的客户就形成了一对多的依赖关系

实现代码如下:

被观察者模式

class Subject {
​constructor() {this.observerList = [];}
​addObserver(observer) {this.observerList.push(observer);}
​removeObserver(observer) {const index = this.observerList.findIndex(o => o.name === observer.name);this.observerList.splice(index, 1);}
​notifyObservers(message) {const observers = this.observeList;observers.forEach(observer => observer.notified(message));}
​
}

观察者:

class Observer {
​constructor(name, subject) {this.name = name;if (subject) {subject.addObserver(this);}}
​notified(message) {console.log(this.name, 'got message', message);}
}

使用代码如下:

const subject = new Subject();
const observerA = new Observer('observerA', subject);
const observerB = new Observer('observerB');
subject.addObserver(observerB);
subject.notifyObservers('Hello from subject');
subject.removeObserver(observerA);
subject.notifyObservers('Hello again');

上述代码中,观察者主动申请加入被观察者的列表,被观察者主动将观察者加入列表

二、发布订阅模式

发布-订阅是一种消息范式,消息的发送者(称为发布者)不会将消息直接发送给特定的接收者(称为订阅者)。而是将发布的消息分为不同的类别,无需了解哪些订阅者(如果有的话)可能存在

同样的,订阅者可以表达对一个或多个类别的兴趣,只接收感兴趣的消息,无需了解哪些发布者存在

实现代码如下:

class PubSub {constructor() {this.messages = {};this.listeners = {};}// 添加发布者publish(type, content) {const existContent = this.messages[type];if (!existContent) {this.messages[type] = [];}this.messages[type].push(content);}// 添加订阅者subscribe(type, cb) {const existListener = this.listeners[type];if (!existListener) {this.listeners[type] = [];}this.listeners[type].push(cb);}// 通知notify(type) {const messages = this.messages[type];const subscribers = this.listeners[type] || [];subscribers.forEach((cb, index) => cb(messages[index]));}
}

发布者代码如下:

class Publisher {constructor(name, context) {this.name = name;this.context = context;}publish(type, content) {this.context.publish(type, content);}
}

订阅者代码如下:

class Subscriber {constructor(name, context) {this.name = name;this.context = context;}subscribe(type, cb) {this.context.subscribe(type, cb);}
}

使用代码如下:

const TYPE_A = 'music';
const TYPE_B = 'movie';
const TYPE_C = 'novel';
​
const pubsub = new PubSub();
​
const publisherA = new Publisher('publisherA', pubsub);
publisherA.publish(TYPE_A, 'we are young');
publisherA.publish(TYPE_B, 'the silicon valley');
const publisherB = new Publisher('publisherB', pubsub);
publisherB.publish(TYPE_A, 'stronger');
const publisherC = new Publisher('publisherC', pubsub);
publisherC.publish(TYPE_C, 'a brief history of time');
​
const subscriberA = new Subscriber('subscriberA', pubsub);
subscriberA.subscribe(TYPE_A, res => {console.log('subscriberA received', res)
});
const subscriberB = new Subscriber('subscriberB', pubsub);
subscriberB.subscribe(TYPE_C, res => {console.log('subscriberB received', res)
});
const subscriberC = new Subscriber('subscriberC', pubsub);
subscriberC.subscribe(TYPE_B, res => {console.log('subscriberC received', res)
});
​
pubsub.notify(TYPE_A);
pubsub.notify(TYPE_B);
pubsub.notify(TYPE_C);

上述代码,发布者和订阅者需要通过发布订阅中心进行关联,发布者的发布动作和订阅者的订阅动作相互独立,无需关注对方,消息派发由发布订阅中心负责

三、区别

两种设计模式思路是一样的,举个生活例子:

  • 观察者模式:某公司给自己员工发月饼发粽子,是由公司的行政部门发送的,这件事不适合交给第三方,原因是“公司”和“员工”是一个整体

  • 发布-订阅模式:某公司要给其他人发各种快递,因为“公司”和“其他人”是独立的,其唯一的桥梁是“快递”,所以这件事适合交给第三方快递公司解决

上述过程中,如果公司自己去管理快递的配送,那公司就会变成一个快递公司,业务繁杂难以管理,影响公司自身的主营业务,因此使用何种模式需要考虑什么情况两者是需要耦合的

两者区别如下图:

  • 在观察者模式中,观察者是知道Subject的,Subject一直保持对观察者进行记录。然而,在发布订阅模式中,发布者和订阅者不知道对方的存在。它们只有通过消息代理进行通信。

  • 在发布订阅模式中,组件是松散耦合的,正好和观察者模式相反。

  • 观察者模式大多数时候是同步的,比如当事件触发,Subject就会去调用观察者的方法。而发布-订阅模式大多数时候是异步的(使用消息队列)

设计模式

设计模式(Design Pattern)是一套被反复使用、多数人知晓的、经过分类的、代码设计经验的总结。

单例模式

单个实例,只有一个对象,多次创建,返回同一个对象。

单例模式的核心:确保只有一个实例,并提供全局访问

发布订阅模式

观察者模式又叫发布-订阅模式,它定义对象间的一种一对多的依赖关系,当一个对象的状态

发生改变时,所有依赖于它的对象都将得到通知。

案例

  1. 流感期间,小明去药店买口罩,然而到店之后却被店员告知口罩已经售罄。这时,小明给店员留了一个电话,告诉店员等口罩到货后打电话通知他一下, 这样小明就可以在口罩到货后的第一时间去药店买口罩了。小明买口罩的过程就是对发布-订阅模式的一次实践:小明将电话留给店员,让店员在口罩到货后通知他,便是一次“订阅”;店员在口罩到货后,打电话告诉小明,便是一次“发布”。不过,在上面的例子中发布-订阅模式并不是唯一的解决方案,其优势也并没有体现出来。比如,小明不想把自己的电话留给药店,而是把药店的电话记了下来,每天给药店打电话去问。从目前的情况看,这种方式也是可以工作的。但往往我们需要面临更复杂的情况:当前疫情严重,不止小明一人需要口罩,小红、小白、小黑等许许多多的人也都需要口罩,他们也都想在口罩到货的第一时间得到消息,于是他们每个人每天都需要给药店打电话询问。这样药店每天都会收到几百个电话,店员们每天都被一个简单却重复的问题搞得很疲惫,而小明也每天都担心电话打晚了会被别人先得到消息“抢”走口罩。当有许多人都想获得“口罩到货”这一相同消息时,发布-订阅模式的优势就显示出来了。只要所有想知道这个消息的用户都在药店留一个电话,当口罩到货后,店员再给“订阅”这一事件的所有用户都打电话告知一下就可以了。这样用户就不用每天都给药店打电话询问,药店也不需要每天都花大量的时间接听电话了。

//发布者-药店
let drugstore = {//可在订阅事件的客户列表clientList: {},//添加事件订阅对象的方法listen(eventName, fn) {if (!this.clientList[eventName]) {//如果客户订阅的事件当前不存在,则初始化这个事件this.clientList[eventName] = [];}//如果存在,将客户端的联系方法添加到数组中this.clientList[eventName].push(fn);},//向订阅对象发布消息的方法publish(eventName, data) {this.clientList[eventName].map(fn => {//将需要发布的数据作为参数,调用客户在订阅时传入的回调函数fn(data);})}
}
//订阅者-用户
//口罩到货后,小明要做的事件
function xiaoMing(data) {console.log('口罩到货了,我要赶紧去买一些!');
}
//小明监听口罩到货事件
drugstore.listen('haveMask', xiaoMing);
​
// 口罩到货后,小红要做的事情
function xiaoHong(data) {if (new Date() > new Date('2022/8/31')) {console.log('疫情应该结束了,不买口罩了')} else {console.log('赶紧去买口罩,不然就买不着了')}
}
// 小红监听口罩到货事件
drugstore.listen('haveMask', xiaoHong);
​
// 口罩到货后,小白要做的事情
function xiaoBai(data) {if (data.price > 100) {console.log('这口罩咋这么贵?不买了!')} else if (data.price > 50) {console.log('这口罩偏贵,先买10个用着,过段时间看能不能降价')} else {console.log('这批口罩价格还可以,买50个屯着')}
}
​
// 小白监听口罩到货事件
drugstore.listen('haveMask', xiaoBai)
​
// 假设到货口罩的相关信息如下
const data = {type: 'N95',price: 30,num: 1000
}
​
// 发布口罩到货的消息,并把口罩的相关信息作为参数传递给订阅该事件回调函数
// 收到信息后具体怎么处理,完全由回调函数自己定义,“药店”并不关心
drugstore.publish('haveMask', data)

这篇关于JavaScript中的发布订阅和观察者模式:如何优雅地处理事件和数据更新的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/476559

相关文章

Java实现检查多个时间段是否有重合

《Java实现检查多个时间段是否有重合》这篇文章主要为大家详细介绍了如何使用Java实现检查多个时间段是否有重合,文中的示例代码讲解详细,感兴趣的小伙伴可以跟随小编一起学习一下... 目录流程概述步骤详解China编程步骤1:定义时间段类步骤2:添加时间段步骤3:检查时间段是否有重合步骤4:输出结果示例代码结语作

Java中String字符串使用避坑指南

《Java中String字符串使用避坑指南》Java中的String字符串是我们日常编程中用得最多的类之一,看似简单的String使用,却隐藏着不少“坑”,如果不注意,可能会导致性能问题、意外的错误容... 目录8个避坑点如下:1. 字符串的不可变性:每次修改都创建新对象2. 使用 == 比较字符串,陷阱满

Java判断多个时间段是否重合的方法小结

《Java判断多个时间段是否重合的方法小结》这篇文章主要为大家详细介绍了Java中判断多个时间段是否重合的方法,文中的示例代码讲解详细,感兴趣的小伙伴可以跟随小编一起学习一下... 目录判断多个时间段是否有间隔判断时间段集合是否与某时间段重合判断多个时间段是否有间隔实体类内容public class D

IDEA编译报错“java: 常量字符串过长”的原因及解决方法

《IDEA编译报错“java:常量字符串过长”的原因及解决方法》今天在开发过程中,由于尝试将一个文件的Base64字符串设置为常量,结果导致IDEA编译的时候出现了如下报错java:常量字符串过长,... 目录一、问题描述二、问题原因2.1 理论角度2.2 源码角度三、解决方案解决方案①:StringBui

Java覆盖第三方jar包中的某一个类的实现方法

《Java覆盖第三方jar包中的某一个类的实现方法》在我们日常的开发中,经常需要使用第三方的jar包,有时候我们会发现第三方的jar包中的某一个类有问题,或者我们需要定制化修改其中的逻辑,那么应该如何... 目录一、需求描述二、示例描述三、操作步骤四、验证结果五、实现原理一、需求描述需求描述如下:需要在

Java中ArrayList和LinkedList有什么区别举例详解

《Java中ArrayList和LinkedList有什么区别举例详解》:本文主要介绍Java中ArrayList和LinkedList区别的相关资料,包括数据结构特性、核心操作性能、内存与GC影... 目录一、底层数据结构二、核心操作性能对比三、内存与 GC 影响四、扩容机制五、线程安全与并发方案六、工程

JavaScript中的reduce方法执行过程、使用场景及进阶用法

《JavaScript中的reduce方法执行过程、使用场景及进阶用法》:本文主要介绍JavaScript中的reduce方法执行过程、使用场景及进阶用法的相关资料,reduce是JavaScri... 目录1. 什么是reduce2. reduce语法2.1 语法2.2 参数说明3. reduce执行过程

如何使用Java实现请求deepseek

《如何使用Java实现请求deepseek》这篇文章主要为大家详细介绍了如何使用Java实现请求deepseek功能,文中的示例代码讲解详细,感兴趣的小伙伴可以跟随小编一起学习一下... 目录1.deepseek的api创建2.Java实现请求deepseek2.1 pom文件2.2 json转化文件2.2

Java调用DeepSeek API的最佳实践及详细代码示例

《Java调用DeepSeekAPI的最佳实践及详细代码示例》:本文主要介绍如何使用Java调用DeepSeekAPI,包括获取API密钥、添加HTTP客户端依赖、创建HTTP请求、处理响应、... 目录1. 获取API密钥2. 添加HTTP客户端依赖3. 创建HTTP请求4. 处理响应5. 错误处理6.

Spring AI集成DeepSeek的详细步骤

《SpringAI集成DeepSeek的详细步骤》DeepSeek作为一款卓越的国产AI模型,越来越多的公司考虑在自己的应用中集成,对于Java应用来说,我们可以借助SpringAI集成DeepSe... 目录DeepSeek 介绍Spring AI 是什么?1、环境准备2、构建项目2.1、pom依赖2.2