带你了解前端设计模式-🍀发布-订阅模式🍀

设计模式是一套被反复使用、多数人知晓、经过分类编目的、代码设计经验的总结。它是为了可重用代码,让代码更容易的被他人理解并保证代码的可靠性。

设计模式合集链接:

带你了解前端设计模式-🍀单例模式🍀

带你了解前端设计模式-🍀工厂模式🍀

带你了解前端设计模式-🍀策略模式🍀

带你了解前端设计模式-🍀代理模式🍀

带你了解前端设计模式-🍀观察者模式🍀

带你了解前端设计模式-🍀发布-订阅模式🍀

概述

发布-订阅是⼀种消息范式,消息的发送者(称为发布者)不会将消息直接发送给特定的接收者(称为订阅者)。⽽是将发布的消息分为不同的类别,⽆需了解哪些订阅者(如果有的话),同样的,订阅者可以表达对⼀个或多个类别的兴趣,只接收感兴趣的消息,⽆需了解哪些发布者存在。

在23种设计模式中没有发布-订阅模式的,其实它是观察者模式的一个别名,但两者又有所不同。这个别名非常形象地诠释了观察者模式里两个核心的角色要素------发布者和订阅者

我们先看下两者的结构图:

观察者模式

发布-订阅模式

这两种模式的最大区别就是发布-订阅模式有一个调度中心,从两者结构图可以看到,观察者模式是由具体目标调度的,而发布-订阅模式是统一由调度中心调的,所以观察者模式的订阅者与发布者之间是存在依赖的,而发布-订阅模式则不会,这就实现了解耦。

两种设计模式思路是⼀样的,举个⽣活例⼦:

观察者模式:某公司给⾃⼰员⼯发⽉饼发粽⼦,是由公司的⾏政部⻔发送的,这件事不适合交给第三⽅,原因是"公司"和"员⼯"是⼀个整体。

发布-订阅模式:某公司要给其他⼈发各种快递,因为"公司"和"其他⼈"是独⽴的,其唯⼀的桥梁是"快递",所以这件事适合交给第三⽅快递公司解决。

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

实现

发布-订阅模式在前端开发中被广泛应用,主要是因为它能够简化组件之间的通信、解耦业务逻辑、实现事件驱动等。

以下是前端中发布-订阅模式的一些常见应用场景:

  • DOM事件处理:在处理 DOM 事件时,发布-订阅模式也非常有用。你可以通过订阅 DOM 元素上的特定事件来触发相应的操作,而不用在每个事件处理函数中手动绑定事件。
  • 事件总线:发布-订阅模式可以用来实现事件总线,方便组件之间的事件通信。例如,Vue.js 中的 EventBus,可以用来在组件之间发布和订阅事件。
  • 全局状态管理:在大型应用中,需要管理全局状态的变化并通知相关组件更新,发布-订阅模式可以作为全局状态管理的一种解决方案。例如,Redux 和 MobX 等状态管理库中就使用了发布-订阅模式。
  • 消息通知:当需要实现消息通知功能时,发布-订阅模式也能发挥作用。例如,实时通知、消息提醒等功能可以通过发布-订阅模式来实现。
  • ......

发布-订阅模式在前端开发中有许多实际应用,可以帮助我们更好地管理和组织组件之间的通信,实现解耦并提高代码的可读性和可维护性。

DOM事件处理

在 DOM 上绑定的事件处理函数 addEventListener就是使用的发布-订阅模式。

在前端开发中,发布-订阅模式可以很好地用于处理 DOM 事件,使得事件的订阅和处理更加灵活和分离。下面是一个简单的发布-订阅模式 DOM 事件处理的示例:

js 复制代码
const eventHandler = {
  events: {}, // 存储事件及对应的处理函数
​
  subscribe(event, callback) {
    if (!this.events[event]) {
      this.events[event] = [];
    }
    this.events[event].push(callback);
  },
​
  publish(event, data) {
    if (!this.events[event]) return;
    this.events[event].forEach(callback => callback(data));
  }
};
​
// 订阅按钮点击事件
eventHandler.subscribe('buttonClick', function() {
  console.log('Button clicked!');
});
​
// 模拟按钮触发点击事件
document.getElementById('button').addEventListener('click', function() {
  eventHandler.publish('buttonClick');
});

在这个示例中,我们首先创建了一个 eventHandler 对象,该对象有两个方法:subscribe 订阅事件和添加处理函数,publish 触发事件并执行对应的处理函数。

然后,我们订阅了一个名为 buttonClick 的事件,并将处理函数打印出相应消息。最后,我们在页面上的按钮上绑定了点击事件,当按钮点击时,会触发 buttonClick 事件并执行对应的处理函数。

这样的设计使得我们能够更加灵活地管理和组织 DOM 事件的处理逻辑,将事件和处理逻辑进行分离,提高了代码的可读性和可维护性。

事件总线

在 Vue.js 中,发布-订阅模式可以通过一个事件总线来实现,方便组件之间的事件通信。这个事件总线可以使用 Vue 实例作为中介来实现发布和订阅事件。

下面是一个简单的发布-订阅模式 Vue 事件总线的示例:

假设有两个组件 ComponentAComponentB,它们之间需要进行事件的订阅和发布。我们可以通过一个全局的 Vue 实例作为事件总线来实现。

首先,创建一个包含事件总线的 eventBus.js 文件:

js 复制代码
// eventBus.js
​
import Vue from 'vue';
​
export const eventBus = new Vue();

然后在 ComponentA 组件中订阅事件:

jsx 复制代码
<template>
  <div>
    <p>Component A</p>
  </div>
</template>
​
<script>
import { eventBus } from './eventBus.js';
​
export default {
  mounted() {
    eventBus.$on('customEvent', (data) => {
      console.log('Event received in Component A:', data);
    });
  }
}
</script>

ComponentB 组件中发布事件:

jsx 复制代码
<template>
  <div>
    <p>Component B</p>
    <button @click="handleButtonClick">Click me to send event</button>
  </div>
</template>
​
<script>
import { eventBus } from './eventBus.js';
​
export default {
  methods: {
    handleButtonClick() {
      eventBus.$emit('customEvent', { message: 'Hello from Component B' });
    }
  }
}
</script>

在这个示例中,我们使用了 eventBus.js 中创建的事件总线,在 ComponentA 中订阅了一个名为 customEvent 的事件,并定义了处理函数来打印收到的事件消息。在 ComponentB 中,当按钮点击时触发了 customEvent 事件,并传递了消息数据。

通过这个方式,我们实现了不同组件之间的事件订阅和发布,使得它们可以在不直接通信的情况下进行交互。这种方式适用于一些简单的组件通信场景,对于复杂的应用建议使用 Vuex 等专门的状态管理工具来处理全局状态。

优缺点

发布-订阅模式最大的优点就是解耦

发布-订阅模式优点:

  1. 解耦:发布-订阅模式可以使发布者和订阅者之间解耦,它们互不依赖于彼此的具体实现,从而降低组件之间的耦合度。
  2. 异步:发布-订阅模式支持异步操作,发布者可以在任何时间发布事件,订阅者也可以在适当的时间订阅事件。这种异步机制非常灵活,适用于需要非阻塞操作的场景。
  3. 动态:发布-订阅模式支持动态添加新的订阅者和取消订阅者,使得系统更加灵活和可扩展。
  4. 可复用性:通过发布-订阅模式可以实现事件的广播,可以让多个订阅者同时接收相同的事件消息,提高了代码的可复用性。

发布-订阅模式缺点:

  1. 内存泄漏:发布-订阅模式在使用过程中需要手动管理订阅和取消订阅,如果不及时取消订阅可能会导致内存泄漏。
  2. 调试困难:因为事件的发布和订阅是分布式的,可能会导致事件流难以追踪,使得调试和代码理解变得困难。
  3. 性能问题:在一些多次触发事件的场景下,发布-订阅模式可能会存在性能问题,因为事件的广播需要循环遍历所有订阅者。
  4. 难以维护:在一个复杂的系统中,如果过度使用发布-订阅模式可能会导致事件流变得难以维护和理解,增加代码的复杂性。

参考文献

观察者模式/发布-订阅模式

相关推荐
Ticnix11 分钟前
ECharts初始化、销毁、resize 适配组件封装(含完整封装代码)
前端·echarts
纯爱掌门人14 分钟前
终焉轮回里,藏着 AI 与人类的答案
前端·人工智能·aigc
twl18 分钟前
OpenClaw 深度技术解析
前端
崔庆才丨静觅21 分钟前
比官方便宜一半以上!Grok API 申请及使用
前端
星光不问赶路人29 分钟前
vue3使用jsx语法详解
前端·vue.js
天蓝色的鱼鱼32 分钟前
shadcn/ui,给你一个真正可控的UI组件库
前端
布列瑟农的星空36 分钟前
前端都能看懂的Rust入门教程(三)——控制流语句
前端·后端·rust
Mr Xu_41 分钟前
Vue 3 中计算属性的最佳实践:提升可读性、可维护性与性能
前端·javascript
jerrywus1 小时前
我写了个 Claude Code Skill,再也不用手动切图传 COS 了
前端·agent·claude
玖月晴空1 小时前
探索关于Spec 和Skills 的一些实战运用-Kiro篇
前端·aigc·代码规范