观察者模式核心概念
-
观察者模式 (Observer Pattern) :
- 是一种行为设计模式,它定义了一种一对多的依赖关系,让多个观察者对象同时监听某一个主题对象。当主题对象的状态发生变化时,会通知所有观察者对象,使它们能够自动更新自己;
- 也常被称为 “发布 - 订阅模式” (注意:严格来说发布订阅是其变种,但前端中常混用);
-
类图:
-
实现代码:
// 观察者接口 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
发布订阅模式(变种)
-
发布订阅模式 (Publish-Subscribe Pattern) 是观察者模式的升级版本,核心改进是引入事件中心 (Event Channel/Broker),彻底解耦发布者和订阅者;
-
类图:
-
实现代码:
// ------------------------ 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] } → 执行自定义逻辑
常用使用场景
事件监听(原生 / 框架)
-
原生 JS:addEventListener 本质就是观察者模式 —— DOM 元素是被观察者,事件处理函数是观察者,触发事件 (如 click/scroll) 时通知所有观察者;
// 示例:按钮点击监听(被观察者:btn,观察者:回调函数) const btn = document.querySelector('#btn'); btn.addEventListener('click', () => console.log('点击了按钮')); // 添加观察者 -
Vue 事件系统:$on/$emit、组件 v-on 绑定事件,底层基于观察者模式实现;
状态管理(Vuex/Pinia/Redux)
-
Vuex 的 store 是被观察者,组件通过 mapState/useStore 订阅状态,状态更新时所有依赖组件自动重新渲染;
-
Redux 的 subscribe 方法允许监听 store 变化,触发组件更新,核心逻辑是观察者模式;
发布 - 订阅库(如 EventEmitter)
-
Node.js 的 EventEmitter 是典型实现,前端常用的 mitt/tiny-emitter 等库,本质是观察者模式的封装;
import mitt from 'mitt'; const emitter = mitt(); // 观察者订阅事件 emitter.on('msg', (data) => console.log('收到消息:', data)); // 被观察者发布事件(通知观察者) emitter.emit('msg', 'Hello World');
异步操作通知(如 Promise / 回调)
-
Promise 的 then/catch 是观察者模式:Promise 实例是被观察者,then 回调是观察者,状态从 pending 变为 fulfilled/rejected 时通知观察者;
-
AJAX 请求回调、WebSocket 消息监听也基于此模式;
自定义组件通信(非父子)
-
当组件无直接嵌套关系时,可通过观察者模式实现跨组件通信 (如 Vue2 的全局事件总线);
// Vue2 全局事件总线示例 Vue.prototype.$bus = new Vue(); // 组件A(观察者) this.$bus.$on('refresh-data', () => this.fetchData()); // 组件B(被观察者) this.$bus.$emit('refresh-data'); -
vue3 可利用 hooks 实现类似功能,或使用 mitt 等库;
观察者模式优缺点
-
✅ 松耦合:主题和观察者之间是抽象耦合;
-
✅ 支持广播通信:主题自动通知所有注册的观察者;
-
✅ 符合开闭原则:无需修改主题代码就能增加新观察者;
-
✅ 可建立触发链:观察者更新可以触发其他主题的更新;
缺点:
-
❌ 通知顺序不确定:观察者更新顺序不可控;
-
❌ 性能问题:观察者过多时通知耗时;
-
❌ 循环依赖风险:可能导致循环调用;
-
❌ 内存泄漏:忘记注销观察者可能导致内存泄漏;
外观模式(结构型模式)
上一篇