观察者模式

实现观察者模式,理解发布-订阅机制

问题

实现观察者模式,让多个观察者监听一个主题对象,当主题状态变化时自动通知所有观察者。

解答

基础实现

// 主题(被观察者)
class Subject {
  constructor() {
    this.observers = []
  }

  // 添加观察者
  addObserver(observer) {
    this.observers.push(observer)
  }

  // 移除观察者
  removeObserver(observer) {
    this.observers = this.observers.filter(obs => obs !== observer)
  }

  // 通知所有观察者
  notify(data) {
    this.observers.forEach(observer => observer.update(data))
  }
}

// 观察者
class Observer {
  constructor(name) {
    this.name = name
  }

  update(data) {
    console.log(`${this.name} 收到通知:`, data)
  }
}

// 使用
const subject = new Subject()

const observer1 = new Observer('观察者1')
const observer2 = new Observer('观察者2')

subject.addObserver(observer1)
subject.addObserver(observer2)

subject.notify({ message: '状态更新了' })
// 观察者1 收到通知: { message: '状态更新了' }
// 观察者2 收到通知: { message: '状态更新了' }

subject.removeObserver(observer1)
subject.notify({ message: '再次更新' })
// 观察者2 收到通知: { message: '再次更新' }

EventEmitter 实现

class EventEmitter {
  constructor() {
    this.events = {}
  }

  // 订阅事件
  on(event, callback) {
    if (!this.events[event]) {
      this.events[event] = []
    }
    this.events[event].push(callback)
    return this
  }

  // 取消订阅
  off(event, callback) {
    if (!this.events[event]) return this
    this.events[event] = this.events[event].filter(cb => cb !== callback)
    return this
  }

  // 触发事件
  emit(event, ...args) {
    if (!this.events[event]) return this
    this.events[event].forEach(callback => callback(...args))
    return this
  }

  // 只订阅一次
  once(event, callback) {
    const wrapper = (...args) => {
      callback(...args)
      this.off(event, wrapper)
    }
    this.on(event, wrapper)
    return this
  }
}

// 使用
const emitter = new EventEmitter()

emitter.on('login', user => {
  console.log(`${user} 登录了`)
})

emitter.once('firstVisit', () => {
  console.log('首次访问,显示引导')
})

emitter.emit('login', '张三')  // 张三 登录了
emitter.emit('firstVisit')     // 首次访问,显示引导
emitter.emit('firstVisit')     // 无输出,因为用的 once

实际应用:数据绑定

// 简单的响应式数据
function reactive(obj, callback) {
  return new Proxy(obj, {
    set(target, key, value) {
      const oldValue = target[key]
      target[key] = value
      // 值变化时通知
      if (oldValue !== value) {
        callback(key, value, oldValue)
      }
      return true
    }
  })
}

// 使用
const state = reactive({ count: 0 }, (key, newVal, oldVal) => {
  console.log(`${key}: ${oldVal} -> ${newVal}`)
})

state.count = 1  // count: 0 -> 1
state.count = 2  // count: 1 -> 2

关键点

  • 一对多关系:一个主题对应多个观察者,解耦发布者和订阅者
  • 主动推送:状态变化时主动通知,观察者无需轮询
  • EventEmitter:Node.js 和浏览器中广泛使用的事件机制
  • 应用场景:DOM 事件、Vue 响应式、Redux 状态管理、WebSocket 消息处理
  • 与发布订阅区别:观察者模式是直接通知,发布订阅有中间调度中心