当前位置: 首页 > news >正文

JavaScript 简单实现观察者模式和发布-订阅模式

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 代码实现

下面就来简单实现一下它的代码。

class Subject {// 被观察者 学生constructor() {this.state = "happy";this.observers = []; // 存储所有的观察者}//新增观察者add(o) {this.observers.push(o);}// 更新状态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.state}`);}
}//创建被观察者学生
let student = new Subject("学生");
//创建观察者父母和老师
let parent = new Observer("父母");
let teacher = new Observer("老师");
//给被观察者学生增加观察者
student.add(parent);
student.add(teacher);student.setState("sad");
//亲爱的父母 通知您当前学生的状态是sad
//亲爱的老师 通知您当前学生的状态是sad

2. 发布-订阅模式

2.1 什么是发布-订阅模式

发布订阅模式跟观察者模式很像,它们其实都有发布者订阅者,但是他们是有区别的:

  • 观察者模式的发布和订阅是互相依赖的
  • 发布订阅模式的发布和订阅是不互相依赖的,因为有一个统一调度中心

为了更好区分这两种设计模式,接着上述例子。

  • 所有老师都希望订阅小明的情绪状态,他们向情绪监测系统注册自己,来时刻关注小明的情绪。(向调度中心订阅事件)
  • 当小明的情绪发生变化时,情绪监测系统会将消息发布给所有订阅了小明情绪状态的老师。例如,如果小明在上课时感到烦躁,情绪监测系统会发布消息给老师:“小明情绪不稳定,请关注他的情绪变化。”(调度中心通知变化)

通过发布订阅模式,小明不需要直接告诉每位老师他的情绪状态,而是通过情绪监测系统自动发布消息给所有订阅了他情绪状态的老师。这种发布者不直接接触到订阅者的模式,就是发布订阅模式。
在这里插入图片描述
那么发布订阅模式有何应用呢?
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", "操场", "开心");

输出结果:

在这里插入图片描述

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", "教室", "上课", "打架", "愤怒");

输出结果:

在这里插入图片描述

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 wrapper = (...args) => {cb(args); this.off(name, wrapper); };this.on(name, wrapper);}
}// 测试
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", "教室", "上课", "打架", "愤怒");

输出结果:

在这里插入图片描述

写作不易,你的一赞一评,就是我前行的最大动力。如有问题,欢迎指出!

相关文章:

JavaScript 简单实现观察者模式和发布-订阅模式

JavaScript 简单实现观察者模式和发布-订阅模式 1. 观察者模式1.1 什么是观察者模式1.2 代码实现 2. 发布-订阅模式2.1 什么是发布-订阅模式2.2 代码实现2.2.1 基础版2.2.2 取消订阅2.2.3 订阅一次 1. 观察者模式 1.1 什么是观察者模式 概念&#xff1a;观察者模式定义对象间…...

java集成短信服务 测试版 qq邮箱简单思路

java集成短信服务 注册一个帐号 使用的是容联云&#xff0c;百度搜一下官网 用手机注册一个帐号就行&#xff0c;免费体验不需要认证 注册后会有八块钱送&#xff0c;可以使用免费的给自己设置三个固定手机号发送短信&#xff0c;不需要认证。 此页面的 三个信息需要在代码中…...

#P0994. [NOIP2004普及组] 花生采摘

题目描述 鲁宾逊先生有一只宠物猴&#xff0c;名叫多多。这天&#xff0c;他们两个正沿着乡间小路散步&#xff0c;突然发现路边的告示牌上贴着一张小小的纸条&#xff1a;“欢迎免费品尝我种的花生&#xff01;――熊字”。 鲁宾逊先生和多多都很开心&#xff0c;因为花生正…...

Elasticsearch和Kibana的安装及验证

金翅大鹏盖世英&#xff0c;展翅金鹏盖世雄。 穿云燕子锡今鸽&#xff0c;踏雪无痕花云平。 ---------------- 2023.7.31.101 ----------------- 本文密钥&#xff1a;365 Elasticsearch 是一个分布式的 RESTful 风格的搜索和数据分析引擎&#xff0c;常用来进行全文检索、…...

细讲TCP三次握手四次挥手(一)

计算机网络体系结构 在计算机网络的基本概念中&#xff0c;分层次的体系结构是最基本的。计算机网络体系结构的抽象概念较多&#xff0c;在学习时要多思考。这些概念对后面的学习很有帮助。 网络协议是什么&#xff1f; 在计算机网络要做到有条不紊地交换数据&#xff0c;就必…...

【linux-zabbix】zabbix-agent启动报错:Daemon never wrote its PID file. Failing.

背景&#xff1a; 发现有部分的agent失联&#xff0c;排查发现机器正常&#xff0c;agent没起来。 排查日志发现&#xff1a; # journalctl -xe -- Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel -- -- Unit zabbix-agent.service has begun start…...

【微信小程序】初始化 wxCharts,调用updateData动态更新数据

要初始化 wxCharts&#xff0c;你需要按照以下步骤进行操作&#xff1a; 首先&#xff0c;确保已将 wx-charts.js 文件正确引入到小程序的相应页面或组件中。可以通过以下方式引入&#xff1a; const wxCharts require(../../../../components/wx-charts.js);请根据你的项目…...

【C语言初阶(19)】实用的 VS 调试技巧

文章目录 Ⅰ 调试的介绍Ⅱ 常用调试快捷键Ⅲ 调试的时候查看程序当前信息⒈查看临时变量的值⒉查看内存信息⒊查看调用堆栈⒋查看汇编信息⒌查看寄存器信息 Ⅳ 观察形参指针指向的数组Ⅴ 易于调试的代码该如何编写⒈const 修饰指针变量⒉良好代码示范 Ⅵ 编程中常见的错误 Ⅰ 调…...

虚拟机之间配置免密登录

目录 一、配置主机名映射 二、虚拟机配置SSH免密登录 三、验证 一、配置主机名映射 即修改/etc/hosts文件&#xff0c;将几台服务器和主机名进行映射。 注意每台服务器都要进行同样的配置。这样在各自服务器下&#xff0c;我们就可以通过主机名访问对应的ip地址了。 当然&…...

【contenteditable属性将元素改为可编辑状态】

元素添加contenteditable属性之后点击即可进入编辑状态 像这种只修改一条属性不必再打开弹框进行编辑&#xff0c;使用contenteditable会很方便 添加失焦、回车、获焦事件 如 <p :contenteditable"item.contenteditable || false"keydown.enter"key($event…...

Android 第三方库CalendarView

Android 第三方库CalendarView 根据需求和库的使用方式&#xff0c;自己弄了一个合适自己的日历&#xff0c;仅记录下&#xff0c;方便下次弄其他样式的日历。地址 需求&#xff1a; 只显示当月的数据 默认的月视图有矩形的线 选中的天数也要有选中的矩形框 今天的item需要…...

钉钉群消息推送

1. 添加钉钉群机器人 PC端登录&#xff08;当前版本手机端无法进行推送关键词设置&#xff09;&#xff0c;群设置--> 机器人 --> webhook进行安全设置复制webhook对应的url 2. 群消息推送 钉钉群消息支持纯文本和markdown类型 2.1 调用示例源码 import com.alibaba.…...

css clip-path 属性介绍

circle() – 圆 语法&#xff1a;circle( [<shape-radius>]? [at <position>]? ) shape-radius 圆的半径 position 圆的中心点位置 使用方法&#xff1a; clip-path: circle(); // 以元素的中心点为圆的中心点&#xff0c;最小宽度一半为圆的半径。clip-path: c…...

Python之pyinstaller打包exe填坑总结

一、起因 编写了一个提取图片中文字的python脚本&#xff0c;想传给同事使用&#xff0c;但是同事电脑上没有任何python环境&#xff0c;更没有安装python库&#xff0c;因此想到通过pyinstaller打包成exe程序传给同事使用&#xff0c;于是开始了不断地挖坑填坑之旅 import p…...

Form Generator 表单JSON数据储存以及JSON回显表单

一、form-generator是什么?✨ ⭐️ 🌟 form-generator的作者是这样介绍的:Element UI表单设计及代码生成器,可将生成的代码直接运行在基于Element的vue项目中;也可导出JSON表单,使用配套的解析器将JSON解析成真实的表单。 但目前它提供的组件并不能满足我们在项目中的…...

Python - OpenCV识别条形码、二维码(已封装,拿来即用)

此代码可识别条形码和二维码&#xff0c;已封装好&#xff0c;拿来即用&#xff1a; import cv2 import pyzbar.pyzbar as pyzbar import numpy from PIL import Image, ImageDraw, ImageFontclass CodeScan():def __init__(self):super(CodeScan, self).__init__()def decode…...

Python如何快速实现爬取网页?

首先我们对要编写的爬虫程序进行简单地分析&#xff0c;该程序可分为以下三个部分&#xff1a; 拼接 url 地址发送请求将照片保存至本地 明确逻辑后&#xff0c;我们就可以正式编写爬虫程序了。 导入所需模块 本节内容使用 urllib 库来编写爬虫&#xff0c;下面导入程序所用…...

怎么才能远程控制笔记本电脑?

为什么选择AnyViewer远程控制软件&#xff1f; 为什么AnyViewer是远程控制笔记本电脑软件的首选&#xff1f;以下是选择AnyViewer成为笔记本电脑远程控制软件的主要因素。 跨平台能力 AnyViewer作为一款跨平台远程控制软件&#xff0c;不仅可以用于从一台Windows电…...

【3】C++实现多进程、多线程

系列文章目录 提示:这里可以添加系列文章的所有文章的目录,目录需要自己手动添加 TODO:写完再整理 文章目录 系列文章目录前言一、同步与互斥1、【thread】同步并发的方法(1)创建、终止、等待、分离线程的方法1.创建线程形式1:使用一个循环执行的函数创建一个线程形式2:…...

Linux用户权限信息、chmod以及chown命令

权限修改 权限信息chmod命令chown命令 权限信息 在Linux系统中&#xff0c;每个文件和目录都包含了权限信息&#xff0c;用于控制对其的访问权限。 文件权限&#xff1a;Linux系统中的文件权限由三组权限表示&#xff0c;分别是所有者权限、组权限和其他用户权限。 所有者权…...

Python|GIF 解析与构建(5):手搓截屏和帧率控制

目录 Python&#xff5c;GIF 解析与构建&#xff08;5&#xff09;&#xff1a;手搓截屏和帧率控制 一、引言 二、技术实现&#xff1a;手搓截屏模块 2.1 核心原理 2.2 代码解析&#xff1a;ScreenshotData类 2.2.1 截图函数&#xff1a;capture_screen 三、技术实现&…...

Linux链表操作全解析

Linux C语言链表深度解析与实战技巧 一、链表基础概念与内核链表优势1.1 为什么使用链表&#xff1f;1.2 Linux 内核链表与用户态链表的区别 二、内核链表结构与宏解析常用宏/函数 三、内核链表的优点四、用户态链表示例五、双向循环链表在内核中的实现优势5.1 插入效率5.2 安全…...

【论文笔记】若干矿井粉尘检测算法概述

总的来说&#xff0c;传统机器学习、传统机器学习与深度学习的结合、LSTM等算法所需要的数据集来源于矿井传感器测量的粉尘浓度&#xff0c;通过建立回归模型来预测未来矿井的粉尘浓度。传统机器学习算法性能易受数据中极端值的影响。YOLO等计算机视觉算法所需要的数据集来源于…...

HBuilderX安装(uni-app和小程序开发)

下载HBuilderX 访问官方网站&#xff1a;https://www.dcloud.io/hbuilderx.html 根据您的操作系统选择合适版本&#xff1a; Windows版&#xff08;推荐下载标准版&#xff09; Windows系统安装步骤 运行安装程序&#xff1a; 双击下载的.exe安装文件 如果出现安全提示&…...

Device Mapper 机制

Device Mapper 机制详解 Device Mapper&#xff08;简称 DM&#xff09;是 Linux 内核中的一套通用块设备映射框架&#xff0c;为 LVM、加密磁盘、RAID 等提供底层支持。本文将详细介绍 Device Mapper 的原理、实现、内核配置、常用工具、操作测试流程&#xff0c;并配以详细的…...

Linux --进程控制

本文从以下五个方面来初步认识进程控制&#xff1a; 目录 进程创建 进程终止 进程等待 进程替换 模拟实现一个微型shell 进程创建 在Linux系统中我们可以在一个进程使用系统调用fork()来创建子进程&#xff0c;创建出来的进程就是子进程&#xff0c;原来的进程为父进程。…...

LINUX 69 FTP 客服管理系统 man 5 /etc/vsftpd/vsftpd.conf

FTP 客服管理系统 实现kefu123登录&#xff0c;不允许匿名访问&#xff0c;kefu只能访问/data/kefu目录&#xff0c;不能查看其他目录 创建账号密码 useradd kefu echo 123|passwd -stdin kefu [rootcode caozx26420]# echo 123|passwd --stdin kefu 更改用户 kefu 的密码…...

Python Einops库:深度学习中的张量操作革命

Einops&#xff08;爱因斯坦操作库&#xff09;就像给张量操作戴上了一副"语义眼镜"——让你用人类能理解的方式告诉计算机如何操作多维数组。这个基于爱因斯坦求和约定的库&#xff0c;用类似自然语言的表达式替代了晦涩的API调用&#xff0c;彻底改变了深度学习工程…...

Qemu arm操作系统开发环境

使用qemu虚拟arm硬件比较合适。 步骤如下&#xff1a; 安装qemu apt install qemu-system安装aarch64-none-elf-gcc 需要手动下载&#xff0c;下载地址&#xff1a;https://developer.arm.com/-/media/Files/downloads/gnu/13.2.rel1/binrel/arm-gnu-toolchain-13.2.rel1-x…...

【UE5 C++】通过文件对话框获取选择文件的路径

目录 效果 步骤 源码 效果 步骤 1. 在“xxx.Build.cs”中添加需要使用的模块 &#xff0c;这里主要使用“DesktopPlatform”模块 2. 添加后闭UE编辑器&#xff0c;右键点击 .uproject 文件&#xff0c;选择 "Generate Visual Studio project files"&#xff0c;重…...