3、前置知识:发布-订阅模式与观察者模式的区别与实现
讲 Vue 响应式原理之前,我觉得有必要先聊聊这两个模式。
为什么?因为 Vue 的响应式系统,说白了就是观察者模式的一个变种。很多同学学到这里容易混淆,我当年也踩过这个坑。记得有一次面试,面试官让我手写一个发布-订阅模式,我写成了观察者模式,结果被问得哑口无言。
嗯,今天咱们就把这两个东西彻底掰扯清楚。
3.1 观察者模式(Observer Pattern)
观察者模式,核心就一句话:被观察者(Subject)直接通知观察者(Observer)。
什么意思?就是 Subject 内部维护了一个观察者列表,当自身状态变化时,遍历这个列表,挨个调用观察者的更新方法。
关键特征:
- Subject 和 Observer 是直接耦合的
- Subject 知道 Observer 的存在
- 通知是同步的,Subject 主动 push
来,看代码。这是我个人习惯的实现方式:
// 被观察者
class Subject {
constructor() {
this.observers = []
this.state = null
}
// 添加观察者
attach(observer) {
this.observers.push(observer)
}
// 移除观察者
detach(observer) {
const idx = this.observers.indexOf(observer)
if (idx > -1) {
this.observers.splice(idx, 1)
}
}
// 状态更新,通知所有观察者
setState(newState) {
this.state = newState
this.notify()
}
notify() {
for (const observer of this.observers) {
observer.update(this.state)
}
}
}
// 观察者
class Observer {
constructor(name) {
this.name = name
}
update(state) {
console.log(`${this.name} 收到状态更新:`, state)
}
}
// 使用
const subject = new Subject()
const observerA = new Observer('观察者A')
const observerB = new Observer('观察者B')
subject.attach(observerA)
subject.attach(observerB)
subject.setState('数据变了')
// 输出:
// 观察者A 收到状态更新: 数据变了
// 观察者B 收到状态更新: 数据变了
你看,Subject 直接调用了 observer.update()。这就是观察者模式的核心——直接依赖。
3.2 发布-订阅模式(Pub-Sub Pattern)
发布-订阅模式就不一样了。它中间多了一层——事件调度中心(Event Bus)。
发布者和订阅者彼此不知道对方的存在。发布者只管往调度中心发消息,订阅者只管从调度中心收消息。调度中心负责匹配和转发。
关键特征:
- 发布者和订阅者完全解耦
- 通过事件中心进行间接通信
- 支持异步和事件类型的区分
我在项目中经常用这种模式来做跨组件通信。比如一个复杂的表单系统,不同模块之间需要传递消息,用 Event Bus 就很清爽。
// 事件调度中心
class EventBus {
constructor() {
this.events = {}
}
// 订阅事件
on(eventName, callback) {
if (!this.events[eventName]) {
this.events[eventName] = []
}
this.events[eventName].push(callback)
// 返回取消订阅的函数
return () => {
this.off(eventName, callback)
}
}
// 取消订阅
off(eventName, callback) {
const callbacks = this.events[eventName]
if (!callbacks) return
const idx = callbacks.indexOf(callback)
if (idx > -1) {
callbacks.splice(idx, 1)
}
}
// 发布事件
emit(eventName, ...args) {
const callbacks = this.events[eventName]
if (!callbacks) return
for (const callback of callbacks) {
callback(...args)
}
}
// 一次性订阅
once(eventName, callback) {
const wrapper = (...args) => {
callback(...args)
this.off(eventName, wrapper)
}
this.on(eventName, wrapper)
}
}
// 使用
const bus = new EventBus()
// 订阅者
const unsubscribe = bus.on('dataChange', (data) => {
console.log('收到数据变化:', data)
})
// 发布者
bus.emit('dataChange', { id: 1, name: '张三' })
// 取消订阅
unsubscribe()
小技巧:我习惯让 on 方法返回一个取消订阅的函数,这样在组件销毁时直接调用 unsubscribe() 就行,不用再手动传 callback 去 off。这个细节在 Vue 组件中特别实用。
3.3 两者的核心区别
来,我直接给你画个对比表,一目了然:
| 对比维度 | 观察者模式 | 发布-订阅模式 |
|---|---|---|
| 耦合关系 | Subject 直接依赖 Observer | 发布者和订阅者完全解耦 |
| 中间层 | 无 | 有事件调度中心 |
| 通信方式 | Subject 主动 push | 通过调度中心转发 |
| 事件类型 | 通常只有一种状态变化 | 支持多种事件类型 |
| 实现复杂度 | 简单 | 相对复杂 |
| 典型应用 | Vue 响应式、MVC | Event Bus、消息队列 |
你想想看,观察者模式就像老师直接点名回答问题。老师(Subject)知道每个学生(Observer)的存在,直接叫名字。而发布-订阅模式就像广播站,你只管播报,谁在听你根本不知道。
3.4 在 Vue 中,用的是哪种?
这个问题我面试过很多人,十个有八个答错。
Vue 的响应式系统,核心是观察者模式。为什么?
- 每个响应式数据(通过 defineReactive 处理)就是一个 Subject
- 每个 Watcher(渲染 Watcher、计算属性 Watcher、用户 Watcher)就是一个 Observer
- 数据变化时,Dep 直接通知所有 Watcher 更新
你看,这不就是 Subject 直接调用 Observer 的 update 方法吗?
注意:Vue 的响应式系统虽然核心是观察者模式,但在一些场景下也用了发布-订阅模式。比如 $on、$emit 就是典型的发布-订阅。所以 Vue 是两种模式混用的,只是响应式部分以观察者模式为主。
3.5 什么时候用哪个?
我个人经验是这样的:
- 用观察者模式:当你知道谁在关注你,而且需要直接、高效地通知。比如 Vue 的响应式,数据变化必须立刻通知对应的 Watcher,延迟不得。
- 用发布-订阅模式:当你不关心谁在接收消息,或者需要解耦多个模块。比如跨组件通信、事件总线、消息队列这些场景。
我曾经在一个项目中,一开始用 Event Bus 做状态管理,结果事件满天飞,调试起来想死的心都有。后来改成观察者模式,把状态变化和视图更新绑定在一起,代码清晰多了。
嗯,这就是这两个模式的核心区别。理解了它们,后面看 Vue 的响应式源码就会轻松很多。因为 Vue 的 Dep、Watcher、Observer 这三个类,本质上就是观察者模式的一个工程化实现。
下一节,我们就正式进入 Vue 响应式原理的源码分析。准备好了吗?