发布订阅模式、观察者模式和 EventBus、EventEmitter

观察者模式

通俗来讲,观察者模式指的是一个对象(Subject)维持一系列依赖于它的对象(Observer),当有关状态发生变更时 Subject 对象则通知一系列 Observer 对象进行更新。

在观察者模式中,Subject 对象拥有添加、删除和通知一系列 Observer 的方法等等,而 Observer 对象拥有更新方法等等。

在 Subject 对象添加了一系列 Observer 对象之后,Subject 对象则维持着这一系列 Observer 对象,当有关状态发生变更时 Subject 对象则会通知这一系列 Observer 对象进行更新。

js 复制代码
// Subject 对象
function Subject() {
  this.observers = [];
};

Subject.prototype = {
  add(observer) {  // 添加
    this.observers.push(observer);
  },
  notify() {  // 通知
    let observers = this.observers;
    for (let i = 0; i < observers.length; i++) {
      observers[i].update();
    }
  },
  remove(observer) {  // 删除
    let observers = this.observers;
    for (let i = 0; i < observers.length; i++) {
      if (observers[i] === observer) {
        observers.splice(i, 1);
      }
    }
  },
};

// Observer 对象
function Observer(name) {
  this.name = name;
};

Observer.prototype = {
  update() {  // 更新
    console.log('my name is ' + this.name);
  }
};

let sub = new Subject();
let obs1 = new Observer('obs1');
let obs2 = new Observer('obs2');
sub.add(obs1);
sub.add(obs2);
sub.notify();  //my name is obs1 name is obs2

发布订阅模式

发布订阅模式指的是希望接收通知的对象(Subscriber)基于一个主题通过自定义事件订阅主题,发布事件的对象(Publisher)通过发布主题事件的方式通知各个订阅该主题的 Subscriber 对象。

js 复制代码
const pubSub = {
  list: {},
  subscribe(key, fn) {  // 订阅
    if (!this.list[key]) {
      this.list[key] = [];
    }
    this.list[key].push(fn);
  },
  publish() {  // 发布
    const arg = arguments;
    const key = Array.prototype.shift.call(arg);
    const fns = this.list[key];

    if (!fns || fns.length <= 0) return false;

    for (let i = 0, len = fns.length; i < len; i++) {
      fns[i].apply(this, arg);
    }
  },
  unSubscribe(key) {  // 取消订阅
    delete this.list[key];
  }
};

// 进行订阅
pubSub.subscribe('name', (name) => {
  console.log('your name is ' + name);
});
pubSub.subscribe('sex', (sex) => {
  console.log('your sex is ' + sex);
});
// 进行发布
pubSub.publish('name', 'nameValue');  // your name is nameValue
pubSub.publish('sex', 'male');  // your sex is male

观察者模式 VS 发布订阅模式

个人理解,观察者模式维护的是一个事件和多个依赖这个事件的对象之间的关系:

csharp 复制代码
event->[obj1,obj2obj3,....]

发布订阅模式是多个事件(主题)和依赖于该事件(主题)的对象之间的关系:

css 复制代码
{  
    event1->[obj1,obj2....],  
    event2->[obj1,obj2.....],
    ....
}

从表面上看:

  • 观察者模式里,只有两个角色------观察者 + 被观察者
  • 发布订阅模式里面,却不仅仅只有发布者和订阅者两个角色,还有一个经常被我们忽略的------Broker

往更深层级讲:

  • 观察者和被观察者,是松耦合的关系
  • 发布者和订阅者,则完全不存在耦合

从使用层面上讲:

  • 观察者模式,多用于单个应用内部
  • 发布订阅模式,则更多的是一种跨应用的模式,比如我们常用的消息中间件

EventBus

EventBus 和发布订阅模式非常相似,比如 Vue 中的 EventBus ,可以通过 $on 来订阅不同类型的主题,通过 $emit 发布不同的主题,该主题的订阅者就会执行相应 callback 函数。

js 复制代码
class EventBus {
  constructor() {
    this.events = {}; // 存储事件及其对应的回调函数列表
  }

  // 订阅事件
  subscribe(eventName, callback) {
    this.events[eventName] = this.events[eventName] || []; // 如果事件不存在,创建一个空的回调函数列表
    this.events[eventName].push(callback); // 将回调函数添加到事件的回调函数列表中
  }

  // 发布事件
  publish(eventName, data) {
    if (this.events[eventName]) {
     this.events[eventName].forEach(callback => {
        callback(data); // 执行回调函数,并传递数据作为参数
      });
    }
  }

  // 取消订阅事件
  unsubscribe(eventName, callback) {
    if (this.events[eventName]) {
      this.events[eventName] = this.events[eventName].filter(cb => cb !== callback); // 过滤掉要取消的回调函数
    }
  }
}

// 使用

// 创建全局事件总线对象
const eventBus = new EventBus();

const callback1 = data => {
  console.log('Callback 1:', data);
};

const callback2 = data => {
  console.log('Callback 2:', data);
};

// 订阅事件
eventBus.subscribe('event1', callback1);
eventBus.subscribe('event1', callback2);

// 发布事件
eventBus.publish('event1', 'Hello, world!');

// 输出:
// Callback 1: Hello, world!
// Callback 2: Hello, world!

// 取消订阅事件
eventBus.unsubscribe('event1', callback1);

// 发布事件
eventBus.publish('event1', 'Goodbye!');

// 输出:
// Callback 2: Goodbye!

EventEmitter

js 复制代码
class EventEmitter {
  constructor() {
    this.events = {}; // 用于存储事件及其对应的回调函数列表
  }

  // 订阅事件
  on(eventName, callback) {
    this.events[eventName] = this.events[eventName] || []; // 如果事件不存在,创建一个空的回调函数列表
    this.events[eventName].push(callback); // 将回调函数添加到事件的回调函数列表中
  }

  // 发布事件
  emit(eventName, data) {
    if (this.events[eventName]) {
      this.events[eventName].forEach(callback => {
        callback(data); // 执行回调函数,并传递数据作为参数
      });
    }
  }

  // 取消订阅事件
  off(eventName, callback) {
    if (this.events[eventName]) {
      this.events[eventName] = this.events[eventName].filter(cb => cb !== callback); // 过滤掉要取消的回调函数
    }
  }
  
  // 添加一次性的事件监听器 
  once(eventName, callback) { 
      const onceCallback = data => { 
          callback(data); // 执行回调函数 
          this.off(eventName, onceCallback); // 在执行后取消订阅该事件 
      }; 
      this.on(eventName, onceCallback); 
  }
}

//  使用
    const emitter = new EventEmitter();

    const callback1 = data => {
      console.log('Callback 1:', data);
    };
   
    const callback2 = data => {
      console.log('Callback 2:', data);
    };
    
    // 添加一次性事件监听器 
    const onceCallback = data => { 
        console.log('Once Callback:', data); 
    };
    
    // 订阅事件
    emitter.on('event1', callback1);
    emitter.on('event1', callback2);
    emitter.once('event1', onceCallback);
    
    // 发布事件
    emitter.emit('event1', 'Hello, world!');

    // 输出:
    // Callback 1: Hello, world!
    // Callback 2: Hello, world!
    // Once Callback: Hello, world!
    
    // 取消订阅事件
    emitter.off('event1', callback1);

    // 发布事件
    emitter.emit('event1', 'Goodbye!');

    // 输出:
    // Callback 2: Goodbye!

EventBus 和 EventEmitter 有什么区别

至于EventBus 和 EventEmitter 有什么区别,目前除了EventEmitter可以添加一次性监听事件,其余的我还没体会到,欢迎各位大佬指点~~

相关推荐
三十而立洋4 天前
Cookie 详解:从产生到安全,一次讲透
前端·javascript
晨米酱4 天前
AGENTS.md:Agent 的上下文策略层
面试·架构·agent
卡布鲁4 天前
把一个 Vite + Vue3 应用塞进 qiankun (React + Umi3) 主站:十个坑的复盘
前端·javascript·react.js
李少兄4 天前
JavaScript 隐式全局变量解析
javascript
彧azz4 天前
Linux 环境下 Redis 学习总结:数据类型、持久化、锁、事务、主从与缓存问题
linux·redis·笔记·学习·面试
沙蒿同学4 天前
我用 Go 搭了一条 AI Agent 流水线:从 1 张商品图到一整套淘宝详情页
前端·javascript·后端
CoderYanger4 天前
A.每日一题:835. 图像重叠
java·开发语言·程序人生·leetcode·面试·职场和发展·学习方法
paopaokaka_luck4 天前
智慧社区综合服务小程序(人脸识别、AI问答、Echarts图形化分析)
前端·javascript·spring boot·spring·数据分析·echarts
爱学习的执念4 天前
助力金九银十1000道软件测试面试题(功能、接口、自动化、WEB、APP.......)附答案
软件测试·面试
小的~~4 天前
银河麒麟V10 ARM部署TDengine(涛思)工业时序数据库安装与测试
面试·程序员创富