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 响应式原理的源码分析。准备好了吗?