JavaScript 实现观察者模式
模式概述
观察者模式属于行为型设计模式,核心在于构建一对多的依赖模型:当目标对象状态发生变动,所有关联的监听对象将自动获得通知并执行相应处理。
组成要素
- Publisher(发布者):管理订阅者集合,负责注册、取消注册及消息广播
- Subscriber(订阅者):声明接收变更通知的标准接口
- ConcretePublisher(具体发布者):承载业务状态,状态变更时触发广播
- ConcreteSubscriber(具体订阅者):实现通知接口,完成自身状态与发布者的同步
完整代码实现
// 发布者抽象层
class Publisher {
constructor() {
this.subscribers = new Set();
}
subscribe(listener) {
if (typeof listener?.onMessage !== 'function') {
throw new TypeError('订阅对象必须实现 onMessage 方法');
}
this.subscribers.add(listener);
return () => this.unsubscribe(listener);
}
unsubscribe(listener) {
this.subscribers.delete(listener);
}
broadcast(payload) {
this.subscribers.forEach(listener => listener.onMessage(payload));
}
}
// 订阅者抽象层
class Subscriber {
onMessage(payload) {
throw new Error('子类必须重写 onMessage 方法');
}
}
// 业务实现:库存监控模块
class InventoryTracker extends Publisher {
#stock = 0;
setStock(quantity) {
const prev = this.#stock;
this.#stock = quantity;
if (prev !== quantity) {
this.broadcast({ current: quantity, previous: prev, timestamp: Date.now() });
}
}
get stock() {
return this.#stock;
}
}
// 业务实现:邮件通知订阅方
class EmailNotifier extends Subscriber {
onMessage({ current, previous }) {
console.log(`[邮件服务] 库存从 ${previous} 调整为 ${current}`);
}
}
// 业务实现:短信预警订阅方
class SmsAlert extends Subscriber {
constructor(limit = 10) {
super();
this.limit = limit;
}
onMessage({ current }) {
if (current < this.limit) {
console.log(`[短信预警] 库存告急,剩余 ${current} 件,请尽快补货`);
}
}
}
调用演示
const tracker = new InventoryTracker();
const email = new EmailNotifier();
const sms = new SmsAlert(15);
// 注册订阅
const unsubscribeEmail = tracker.subscribe(email);
tracker.subscribe(sms);
tracker.setStock(20); // [邮件服务] 库存从 0 调整为 20
tracker.setStock(12); // [邮件服务] 库存从 20 调整为 12
// [短信预警] 库存告急,剩余 12 件,请尽快补货
// 取消邮件订阅
unsubscribeEmail();
tracker.setStock(8); // [短信预警] 库存告急,剩余 8 件,请尽快补货
核心优势分析
降低耦合强度
发布方仅维护订阅者列表的引用,无需了解具体订阅者的业务逻辑。订阅方可独立演进,不影响发布方代码。
动态关系绑定
运行时自由增减订阅者,系统灵活性显著提升。一对多的通知机制避免了显式遍历调用。
状态自动同步
消除手动同步各对象状态的繁琐操作,降低因遗漏更新导致的数据不一致风险。
与发布/订阅模式的对比
| 维度 | 观察者模式 | 发布/订阅模式 |
|---|---|---|
| 架构关系 | 发布者直接持有订阅者引用 | 引入事件总线作为中介 |
| 耦合程度 | 松耦合,彼此知晓存在 | 完全解耦,互不可见 |
| 交互方式 | 直接调用订阅者的方法 | 通过主题通道传递消息 |
| 典型场景 | 组件内部状态联动 | 跨模块、跨系统的消息通信 |
增强功能扩展
// 支持异步广播,避免阻塞主流程
async broadcastAsync(payload) {
const tasks = Array.from(this.subscribers).map(
listener => Promise.resolve().then(() => listener.onMessage(payload))
);
await Promise.allSettled(tasks);
}
// 支持按条件过滤广播对象
broadcastWhere(payload, predicate) {
this.subscribers.forEach(listener => {
if (predicate(listener)) {
listener.onMessage(payload);
}
});
}
// 支持订阅优先级(高优先级先执行)
subscribeWithPriority(listener, priority = 0) {
listener._priority = priority;
this.subscribers.add(listener);
this._sortByPriority();
}
_sortByPriority() {
this.subscribers = new Set(
[...this.subscribers].sort((a, b) => (b._priority || 0) - (a._priority || 0))
);
}
浏览器原生 API 中的观察者
// ResizeObserver:监听元素尺寸变化
const ro = new ResizeObserver(entries => {
for (const entry of entries) {
const { width, height } = entry.contentRect;
console.log(`元素尺寸: ${width}x${height}`);
}
});
ro.observe(document.querySelector('.responsive-box'));
// PerformanceObserver:监听性能指标
const po = new PerformanceObserver(list => {
for (const entry of list.getEntries()) {
console.log('性能条目:', entry.name, entry.duration);
}
});
po.observe({ entryTypes: ['measure', 'mark'] });
工程实践建议
在构建复杂应用时,若需处理高频数据流、异步编排或背压控制,推荐采用专业响应式库:
- RxJS:提供丰富的操作符与调度策略,适合复杂事件流处理
- MobX:基于代理的自动依赖追踪,简化状态与视图的绑定
对于简单场景,手写观察者模式足以满足需求,能有效控制依赖体积。