观察者模式(Observer Pattern)------前端组件通信的「广播电台」
本文是《前端设计模式魔法手册》系列第 1 篇,完整代码示例可在 GitHub仓库 获取。
一、模式简介
1.1 什么是观察者模式?
观察者模式 是一种行为设计模式,它定义了一种 一对多 的依赖关系:
- 当一个对象(称为 Subject,主题/被观察者)的状态发生改变时
- 所有依赖它的对象(称为 Observers,观察者)都会自动收到通知并更新
1.2 类比现实生活
- 📻 广播电台(Subject):只管发射信号,不关心谁在收听
- 🎧 听众(Observers):随时可以切换频道或关闭收音机
- 🔄 松耦合:电台和听众互不干扰,仅通过信号频率联系
二、前端为何需要观察者模式?
2.1 解决痛点
问题场景 | 观察者模式的解决方案 |
---|---|
组件A需要通知组件B,但两者没有直接引用关系 | 通过中间Subject通信,避免硬编码依赖 |
多处UI需要同步同一份数据(如用户登录状态) | 状态变更时自动批量通知所有观察者 |
异步事件(如WebSocket推送)需要触发多个处理逻辑 | 观察者各自订阅事件,避免回调地狱 |
2.2 典型应用场景
- 🔔 实时数据更新:股票价格、聊天消息、通知中心
- 🎛️ UI组件协同:表单验证、步骤向导、多标签页同步
- 🛠️ 框架核心机制:Vue响应式、Redux状态订阅、React Context
三、模式实现
3.1 基础实现(TypeScript)
typescript
// 主题接口
interface Subject {
addObserver(observer: Observer): void;
removeObserver(observer: Observer): void;
notify(): void;
}
// 观察者接口
interface Observer {
update(data: any): void;
}
// 具体主题:消息通知中心
class MessageNotifier implements Subject {
private observers: Observer[] = [];
private messageCount: number = 0;
addObserver(observer: Observer) {
this.observers.push(observer);
}
removeObserver(observer: Observer) {
this.observers = this.observers.filter(obs => obs !== observer);
}
notify() {
this.observers.forEach(observer =>
observer.update(this.messageCount)
);
}
// 业务方法
setMessageCount(count: number) {
this.messageCount = count;
this.notify(); // 关键点:状态变化时自动通知
}
}
// 具体观察者:导航栏
class NavBar implements Observer {
update(count: number) {
console.log(`NavBar收到消息数: ${count}`);
// 实际开发中更新DOM或组件状态
}
}
3.2 使用示例
typescript
const notifier = new MessageNotifier();
const navBar = new NavBar();
const sidebar = new Sidebar(); // 假设已实现
// 订阅
notifier.addObserver(navBar);
notifier.addObserver(sidebar);
// 触发更新
notifier.setMessageCount(5);
// 输出:
// NavBar收到消息数: 5
// Sidebar收到消息数: 5
四、前端实战案例
4.1 自定义事件总线(Event Bus)
typescript
class EventBus {
private events: Record<string, Function[]> = {};
on(event: string, callback: Function) {
if (!this.events[event]) this.events[event] = [];
this.events[event].push(callback);
}
emit(event: string, payload?: any) {
this.events[event]?.forEach(cb => cb(payload));
}
}
// 使用
const bus = new EventBus();
bus.on('login', (user) => {
console.log(`头部导航更新用户: ${user.name}`);
});
bus.emit('login', { name: 'Alice' });
4.2 Vue响应式原理简化版
javascript
// 模拟Vue的Dep和Watcher机制
class Dep {
subscribers = new Set();
depend() { /* 收集依赖 */ }
notify() { /* 触发更新 */ }
}
// 数据劫持
function defineReactive(obj, key) {
const dep = new Dep();
let value = obj[key];
Object.defineProperty(obj, key, {
get() {
dep.depend(); // 当前正在执行的Watcher会被收集
return value;
},
set(newVal) {
value = newVal;
dep.notify(); // 通知所有Watcher更新
}
});
}
五、优劣分析
✅ 优点
- 解耦:发布者与订阅者无需相互引用
- 动态关系:运行时可以随时添加/移除观察者
- 广播通信:一次变更通知多个对象
⚠️ 注意事项
- 性能问题:观察者过多时,通知遍历可能耗时
- 内存泄漏:忘记取消订阅会导致对象无法回收
- 调试困难:通知链路复杂时难以追踪调用栈
六、扩展知识
6.1 观察者 vs 发布-订阅
观察者模式:
graph TD
Subject --直接通知--> Observer
发布订阅模式:
graph TD
Publisher --通过Broker--> Subscriber
6.2 浏览器内置观察者API
API | 用途 |
---|---|
MutationObserver |
监听DOM变化 |
IntersectionObserver |
监听元素可见性 |
ResizeObserver |
监听元素尺寸变化 |
PerformanceObserver |
监听性能指标 |
下一篇预告 :
《装饰器模式:如何给前端组件动态「加Buff」?》
讨论题 :
在你们项目中,哪些场景可以用观察者模式优化?欢迎在评论区分享案例!