观察者模式核心概念

  1. 观察者模式 (Observer Pattern)

    1. 是一种行为设计模式,它定义了一种一对多的依赖关系,让多个观察者对象同时监听某一个主题对象。当主题对象的状态发生变化时,会通知所有观察者对象,使它们能够自动更新自己;
    2. 也常被称为 “发布 - 订阅模式” (注意:严格来说发布订阅是其变种,但前端中常混用)
  2. 类图:

  3. 实现代码:

    // 观察者接口
    interface Observer {
      update(subject: Subject): void;
    }
    
    // 主题接口
    interface Subject {
      attach(observer: Observer): void;
      detach(observer: Observer): void;
      notify(): void;
    }
    
    // 具体主题 - 新闻发布系统
    class NewsPublisher implements Subject {
      private observers: Observer[] = [];
      private latestNews: string = '';
      private newsCount: number = 0;
    
      // 获取当前状态
      getLatestNews(): string {
        return this.latestNews;
      }
    
      getNewsCount(): number {
        return this.newsCount;
      }
    
      // 设置新状态并通知观察者
      publishNews(news: string): void {
        this.latestNews = news;
        this.newsCount++;
        console.log(`\n📰 新闻发布: "${news}"`);
        this.notify();
      }
    
      // 添加观察者
      attach(observer: Observer): void {
        const isExist = this.observers.includes(observer);
        if (isExist) {
          console.log('观察者已存在');
          return;
        }
        this.observers.push(observer);
        console.log('✅ 观察者添加成功');
      }
    
      // 移除观察者
      detach(observer: Observer): void {
        const observerIndex = this.observers.indexOf(observer);
        if (observerIndex === -1) {
          console.log('观察者不存在');
          return;
        }
        this.observers.splice(observerIndex, 1);
        console.log('❌ 观察者移除成功');
      }
    
      // 通知所有观察者
      notify(): void {
        console.log(`📢 通知 ${this.observers.length} 个观察者...`);
        for (const observer of this.observers) {
          observer.update(this);
        }
      }
    }
    
    // 具体观察者A - 邮件订阅者
    class EmailSubscriber implements Observer {
      private name: string;
      private email: string;
      private lastNewsReceived: string = '';
    
      constructor(name: string, email: string) {
        this.name = name;
        this.email = email;
      }
    
      update(subject: Subject): void {
        // 确保是 NewsPublisher 类型
        if (subject instanceof NewsPublisher) {
          this.lastNewsReceived = subject.getLatestNews();
          console.log(`📧 [邮件订阅者 ${this.name}] 收到新闻: "${this.lastNewsReceived}"`);
          console.log(`   发送邮件到: ${this.email}`);
        }
      }
    
      getLastNews(): string {
        return this.lastNewsReceived;
      }
    }
    
    // 具体观察者B - 手机App用户
    class MobileAppUser implements Observer {
      private userId: string;
      private deviceType: string;
      private notifications: string[] = [];
    
      constructor(userId: string, deviceType: string) {
        this.userId = userId;
        this.deviceType = deviceType;
      }
    
      update(subject: Subject): void {
        if (subject instanceof NewsPublisher) {
          const news = subject.getLatestNews();
          this.notifications.push(news);
          console.log(`📱 [App用户 ${this.userId}] 收到推送通知`);
          console.log(`   设备类型: ${this.deviceType}, 新闻: "${news}"`);
          console.log(`   未读消息数: ${this.notifications.length}`);
        }
      }
    
      getNotifications(): string[] {
        return this.notifications;
      }
    }
    
    // 具体观察者C - 短信订阅者
    class SMSSubscriber implements Observer {
      private phoneNumber: string;
      private isActive: boolean = true;
    
      constructor(phoneNumber: string) {
        this.phoneNumber = phoneNumber;
      }
    
      setActive(status: boolean): void {
        this.isActive = status;
      }
    
      update(subject: Subject): void {
        if (!this.isActive) {
          console.log(`📵 [短信用户 ${this.phoneNumber}] 已停机,无法接收`);
          return;
        }
    
        if (subject instanceof NewsPublisher) {
          console.log(`📟 [短信用户 ${this.phoneNumber}] 收到短信通知`);
          console.log(`   内容: ${subject.getLatestNews().substring(0, 20)}...`);
        }
      }
    }
    
    // 测试代码
    console.log("=== 观察者模式演示 ===\n");
    
    // 创建主题
    const newsPublisher = new NewsPublisher();
    
    // 创建观察者
    const emailSubscriber = new EmailSubscriber("张三", "zhangsan@email.com");
    const mobileUser = new MobileAppUser("U1001", "iPhone 14");
    const smsUser = new SMSSubscriber("13800138000");
    
    // 注册观察者
    console.log("1. 注册观察者:");
    newsPublisher.attach(emailSubscriber);
    newsPublisher.attach(mobileUser);
    newsPublisher.attach(smsUser);
    
    // 发布第一条新闻
    console.log("\n2. 发布第一条新闻:");
    newsPublisher.publishNews("TypeScript 5.0 正式发布!");
    
    // 发布第二条新闻
    console.log("\n3. 发布第二条新闻:");
    newsPublisher.publishNews("2024 前端框架趋势分析");
    
    // 移除一个观察者
    console.log("\n4. 移除短信订阅者:");
    newsPublisher.detach(smsUser);
    
    // 发布第三条新闻
    console.log("\n5. 发布第三条新闻:");
    newsPublisher.publishNews("Vue 3.4 版本更新说明");
    
    // 显示各个观察者的状态
    console.log("\n=== 最终状态 ===");
    console.log(`邮件订阅者最后收到的新闻: "${emailSubscriber.getLastNews()}"`);
    console.log(`App用户未读消息数: ${mobileUser.getNotifications().length}`);
    
    === 观察者模式演示 ===
    
    1. 注册观察者:
    ✅ 观察者添加成功
    ✅ 观察者添加成功
    ✅ 观察者添加成功
    
    2. 发布第一条新闻:
    
    📰 新闻发布: "TypeScript 5.0 正式发布!"
    📢 通知 3 个观察者...
    📧 [邮件订阅者 张三] 收到新闻: "TypeScript 5.0 正式发布!"
      发送邮件到: zhangsan@email.com
    📱 [App用户 U1001] 收到推送通知
      设备类型: iPhone 14, 新闻: "TypeScript 5.0 正式发布!"
      未读消息数: 1
    📟 [短信用户 13800138000] 收到短信通知
      内容: TypeScript 5.0 正式...
    
    3. 发布第二条新闻:
    
    📰 新闻发布: "2024 前端框架趋势分析"
    📢 通知 3 个观察者...
    📧 [邮件订阅者 张三] 收到新闻: "2024 前端框架趋势分析"
      发送邮件到: zhangsan@email.com
    📱 [App用户 U1001] 收到推送通知
      设备类型: iPhone 14, 新闻: "2024 前端框架趋势分析"
      未读消息数: 2
    📟 [短信用户 13800138000] 收到短信通知
      内容: 2024 前端框架趋势...
    
    4. 移除短信订阅者:
    ❌ 观察者移除成功
    
    5. 发布第三条新闻:
    
    📰 新闻发布: "Vue 3.4 版本更新说明"
    📢 通知 2 个观察者...
    📧 [邮件订阅者 张三] 收到新闻: "Vue 3.4 版本更新说明"
      发送邮件到: zhangsan@email.com
    📱 [App用户 U1001] 收到推送通知
      设备类型: iPhone 14, 新闻: "Vue 3.4 版本更新说明"
      未读消息数: 3
    
    === 最终状态 ===
    邮件订阅者最后收到的新闻: "Vue 3.4 版本更新说明"
    App用户未读消息数: 3
    

发布订阅模式(变种)

  1. 发布订阅模式 (Publish-Subscribe Pattern) 是观察者模式的升级版本,核心改进是引入事件中心 (Event Channel/Broker),彻底解耦发布者和订阅者;

  2. 类图:

  3. 实现代码:

    // ------------------------ 1. 实现 EventBus(事件中心,对应类图的 EventBus) ------------------------
    class EventBus {
      // 存储事件名到回调函数列表的映射:key=事件名,value=该事件的所有订阅回调
      private events: Record<string, Function[]> = {};
    
      /**
      * 订阅事件(核心方法)
      * @param eventName 要订阅的事件名
      * @param callback 事件触发时执行的回调函数
      */
      public subscribe(eventName: string, callback: Function): void {
        // 若事件不存在,初始化空数组
        if (!this.events[eventName]) {
          this.events[eventName] = [];
        }
        // 避免重复订阅同一个回调
        if (!this.events[eventName].includes(callback)) {
          this.events[eventName].push(callback);
          console.log(`[EventBus] 订阅事件 "${eventName}",当前订阅数: ${this.events[eventName].length}`);
        }
      }
    
      /**
      * 取消订阅事件
      * @param eventName 要取消的事件名
      * @param callback 要移除的回调(不传则清空该事件所有订阅)
      */
      public unsubscribe(eventName: string, callback?: Function): void {
        if (!this.events[eventName]) return;
    
        if (callback) {
          // 过滤掉要移除的回调
          this.events[eventName] = this.events[eventName].filter(cb => cb !== callback);
          console.log(`[EventBus] 取消订阅事件 "${eventName}" 的指定回调`);
        } else {
          // 清空该事件所有订阅
          delete this.events[eventName];
          console.log(`[EventBus] 清空事件 "${eventName}" 的所有订阅`);
        }
      }
    
      /**
      * 发布事件(核心方法)
      * @param eventName 要发布的事件名
      * @param args 传递给订阅回调的参数(支持多参数)
      */
      public publish(eventName: string, ...args: any[]): void {
        if (!this.events[eventName]) {
          console.log(`[EventBus] 事件 "${eventName}" 暂无订阅者`);
          return;
        }
        console.log(`[EventBus] 发布事件 "${eventName}",通知 ${this.events[eventName].length} 个订阅者`);
        // 遍历执行所有订阅回调,传递参数
        this.events[eventName].forEach(callback => callback(...args));
      }
    }
    
    // -------------------------- 2. 实现 Publisher(发布者,对应类图的 Publisher) -----------------------
    class Publisher {
      // 持有事件中心实例(依赖注入,解耦)
      private eventBus: EventBus;
    
      constructor(eventBus: EventBus) {
        this.eventBus = eventBus;
      }
    
      /**
      * 发布指定事件(封装发布逻辑,对外提供简单接口)
      * @param eventName 事件名
      * @param data 要传递的事件数据
      */
      public publishEvent(eventName: string, data: any): void {
        console.log(`[Publisher] 准备发布事件 "${eventName}",数据:`, data);
        this.eventBus.publish(eventName, data);
      }
    }
    
    // -------------------------- 3. 实现 Subscriber(订阅者,对应类图的 Subscriber) --------------------
    class Subscriber {
      // 订阅者名称(用于日志区分)
      private name: string;
      // 持有事件中心实例
      private eventBus: EventBus;
    
      constructor(name: string, eventBus: EventBus) {
        this.name = name;
        this.eventBus = eventBus;
      }
    
      /**
      * 订阅指定事件(封装订阅逻辑)
      * @param eventName 事件名
      */
      public subscribeEvent(eventName: string): void {
        // 定义当前订阅者的事件处理回调
        const handleEvent = (data: any) => {
          this.handleEvent(data);
        };
        // 订阅事件,并绑定回调(注意:需保存回调引用,用于取消订阅)
        this.eventBus.subscribe(eventName, handleEvent);
        // 给实例挂载回调,方便后续取消订阅(TS 需扩展类型)
        (this as any)._eventCallbacks = (this as any)._eventCallbacks || {};
        (this as any)._eventCallbacks[eventName] = handleEvent;
      }
    
      /**
      * 取消订阅指定事件
      * @param eventName 事件名
      */
      public unsubscribeEvent(eventName: string): void {
        const handleEvent = (this as any)._eventCallbacks?.[eventName];
        if (handleEvent) {
          this.eventBus.unsubscribe(eventName, handleEvent);
        }
      }
    
      /**
      * 事件处理核心逻辑(订阅者的自定义业务)
      * @param data 事件传递的数据
      */
      public handleEvent(data: any): void {
        console.log(`[Subscriber-${this.name}] 收到事件数据:`, data, "→ 执行自定义逻辑");
      }
    }
    
    // -------------------------- 测试代码(验证功能) --------------------------
    // 1. 创建全局事件中心实例
    const globalEventBus = new EventBus();
    
    // 2. 创建发布者实例(可创建多个,共用一个事件中心)
    const publisher1 = new Publisher(globalEventBus);
    const publisher2 = new Publisher(globalEventBus);
    
    // 3. 创建订阅者实例(可创建多个,订阅同一/不同事件)
    const subscriberA = new Subscriber("A", globalEventBus);
    const subscriberB = new Subscriber("B", globalEventBus);
    
    // 4. 订阅事件
    subscriberA.subscribeEvent("user:login"); // 订阅者A订阅「用户登录」事件
    subscriberB.subscribeEvent("user:login"); // 订阅者B订阅「用户登录」事件
    subscriberB.subscribeEvent("order:pay");  // 订阅者B订阅「订单支付」事件
    
    // 5. 发布事件
    publisher1.publishEvent("user:login", { userId: 1001, time: new Date() }); // 发布「用户登录」事件
    // 输出:
    // [EventBus] 订阅事件 "user:login",当前订阅数: 1
    // [EventBus] 订阅事件 "user:login",当前订阅数: 2
    // [EventBus] 订阅事件 "order:pay",当前订阅数: 1
    // [Publisher] 准备发布事件 "user:login",数据: { userId: 1001, time: [Date] }
    // [EventBus] 发布事件 "user:login",通知 2 个订阅者
    // [Subscriber-A] 收到事件数据: { userId: 1001, time: [Date] } → 执行自定义逻辑
    // [Subscriber-B] 收到事件数据: { userId: 1001, time: [Date] } → 执行自定义逻辑
    
    publisher2.publishEvent("order:pay", { orderId: 2002, amount: 99 }); // 发布「订单支付」事件
    // 输出:
    // [Publisher] 准备发布事件 "order:pay",数据: { orderId: 2002, amount: 99 }
    // [EventBus] 发布事件 "order:pay",通知 1 个订阅者
    // [Subscriber-B] 收到事件数据: { orderId: 2002, amount: 99 } → 执行自定义逻辑
    
    // 6. 取消订阅
    subscriberB.unsubscribeEvent("user:login"); // 订阅者B取消「用户登录」事件
    publisher1.publishEvent("user:login", { userId: 1002, time: new Date() }); // 再次发布「用户登录」事件
    // 输出:
    // [EventBus] 取消订阅事件 "user:login" 的指定回调
    // [Publisher] 准备发布事件 "user:login",数据: { userId: 1002, time: [Date] }
    // [EventBus] 发布事件 "user:login",通知 1 个订阅者
    // [Subscriber-A] 收到事件数据: { userId: 1002, time: [Date] } → 执行自定义逻辑
    

常用使用场景

事件监听(原生 / 框架)

  1. 原生 JSaddEventListener 本质就是观察者模式 —— DOM 元素是被观察者,事件处理函数是观察者,触发事件 (如 click/scroll) 时通知所有观察者;

    // 示例:按钮点击监听(被观察者:btn,观察者:回调函数)
    const btn = document.querySelector('#btn');
    btn.addEventListener('click', () => console.log('点击了按钮')); // 添加观察者
    
  2. Vue 事件系统:$on/$emit、组件 v-on 绑定事件,底层基于观察者模式实现;

状态管理(Vuex/Pinia/Redux)

  1. Vuexstore 是被观察者,组件通过 mapState/useStore 订阅状态,状态更新时所有依赖组件自动重新渲染;

  2. Reduxsubscribe 方法允许监听 store 变化,触发组件更新,核心逻辑是观察者模式;

发布 - 订阅库(如 EventEmitter)

  1. Node.jsEventEmitter 是典型实现,前端常用的 mitt/tiny-emitter 等库,本质是观察者模式的封装;

    import mitt from 'mitt';
    
    const emitter = mitt();
    // 观察者订阅事件
    emitter.on('msg', (data) => console.log('收到消息:', data));
    // 被观察者发布事件(通知观察者)
    emitter.emit('msg', 'Hello World');
    

异步操作通知(如 Promise / 回调)

  1. Promisethen/catch 是观察者模式:Promise 实例是被观察者,then 回调是观察者,状态从 pending 变为 fulfilled/rejected 时通知观察者;

  2. AJAX 请求回调、WebSocket 消息监听也基于此模式;

自定义组件通信(非父子)

  1. 当组件无直接嵌套关系时,可通过观察者模式实现跨组件通信 (如 Vue2 的全局事件总线)

    // Vue2 全局事件总线示例
    Vue.prototype.$bus = new Vue();
    // 组件A(观察者)
    this.$bus.$on('refresh-data', () => this.fetchData());
    // 组件B(被观察者)
    this.$bus.$emit('refresh-data');
    
  2. vue3 可利用 hooks 实现类似功能,或使用 mitt 等库;

观察者模式优缺点

  1. ✅ 松耦合:主题和观察者之间是抽象耦合;

  2. ✅ 支持广播通信:主题自动通知所有注册的观察者;

  3. ✅ 符合开闭原则:无需修改主题代码就能增加新观察者;

  4. ✅ 可建立触发链:观察者更新可以触发其他主题的更新;

缺点:

  1. ❌ 通知顺序不确定:观察者更新顺序不可控;

  2. ❌ 性能问题:观察者过多时通知耗时;

  3. ❌ 循环依赖风险:可能导致循环调用;

  4. ❌ 内存泄漏:忘记注销观察者可能导致内存泄漏;

打赏作者
您的打赏是我前进的动力
微信
支付宝
评论

你好👏🏻,我是 ✍🏻   疯狂 codding 中...

粽子

这有关于前端开发的技术文档和你分享。

相信你可以在这里找到对你有用的知识和教程。

了解更多

目录

  1. 1. 观察者模式核心概念
  2. 2. 发布订阅模式(变种)
  3. 3. 常用使用场景
    1. 3.1. 事件监听(原生 / 框架)
    2. 3.2. 状态管理(Vuex/Pinia/Redux)
    3. 3.3. 发布 - 订阅库(如 EventEmitter)
    4. 3.4. 异步操作通知(如 Promise / 回调)
    5. 3.5. 自定义组件通信(非父子)
  4. 4. 观察者模式优缺点