概念

观察者模式(Observer Pattern)是一种行为型设计模式,它定义了一种一对多的依赖关系,让多个观察者对象同时监听并收到目标对象的状态变化


观察者模式通常涉及两个核心角色:目标(Subject)和观察者(Observer)

  • 目标(Subject)

    也称为主题或者可观察对象,它是被观察的对象

    目标对象会维护一个观察者列表,并提供方法来添加、移除和通知观察者

  • 观察者(Observer)

    也称为订阅者或者监听者,它是接收目标对象状态变化通知的对象

    观察者对象通常会实现一个更新方法,用于接收目标对象状态变化的通知


观察者模式的核心思想是通过定义目标对象和观察者对象之间的一对多依赖关系,使得目标对象状态变化时能够通知所有的观察者对象

这种模式的优点在于,实现了目标对象和观察者对象的解耦,使得它们可以独立变化,同时也提供了一种灵活的机制来实现发布-订阅模式

观察者模式适用于以下情况:

  • 当一个对象的状态变化需要通知其他对象,并且这些对象的数量和类型在运行时可以动态变化时,可以使用观察者模式
  • 当一个对象的状态变化可能引起其他对象的状态变化,并且希望避免对象之间的紧耦合关系时,观察者模式也是一个很好的选择


举个简单的例子,考虑一个新闻发布系统

新闻发布者是目标对象,而订阅者是观察者对象。当新闻发布者发布了新的新闻时,所有订阅者都会收到新闻通知,并更新自己的状态

观察者模式可以很好地实现这种发布-订阅的机制,同时也保持了发布者和订阅者之间的解耦


实现条件

  1. 存在一个一对多的依赖关系

    观察者模式适用于存在一个主题(Subject)对象和多个观察者(Observer)对象之间的一对多的依赖关系的情况,当主题对象的状态发生变化时,所有依赖于该主题对象的观察者对象都会得到通知

  2. 主题对象的状态发生变化需要通知观察者对象

    观察者模式适用于主题对象的状态发生变化时需要通知所有观察者对象的情况,观察者对象根据主题对象的状态变化来更新自己的状态或者执行相应的操作

  3. 观察者对象的数量相对固定

    观察者模式适用于观察者对象的数量相对固定的情况,因为主题对象需要维护观察者对象的引用,并且在状态发生变化时通知所有观察者对象

  4. 观察者对象与主题对象之间的解耦

    观察者模式将主题对象和观察者对象之间的关系进行了解耦,使得主题对象和观察者对象可以相互独立地变化,而不需要相互了解具体的实现细节


优点

  1. 简化事件管理

    在前端开发中,观察者模式可以用于简化事件管理。主题对象就是事件源,而观察者对象就是事件处理函数

    当事件源状态变化时,观察者(事件处理函数)会得到通知并执行相应的操作,从而使得事件管理更加简单清晰

  2. 模块解耦

    观察者模式可以帮助将模块解耦,使得代码更具灵活性和可维护性

    通过观察者模式,主题对象(被观察者)和观察者对象之间的依赖关系被解耦,它们可以独立地变化和扩展

  3. 数据驱动界面更新

    在前端应用中,观察者模式常用于数据驱动界面更新

    当数据发生变化时,主题对象会通知所有的观察者对象,而这些观察者对象可以负责更新相应的界面元素,实现了数据和界面的分离

  4. 事件监听

    在前端开发中,观察者模式常用于事件监听。通过注册事件监听器(观察者),可以在事件发生时执行相应的操作,从而实现事件驱动的编程模型

  5. 组件通信

    观察者模式也常被用于组件间的通信。当一个组件的状态发生变化时,可以通过观察者模式通知其他组件进行相应的更新或者操作,从而实现组件间的解耦和通信


缺点

  1. 可能引起内存泄漏

    如果观察者对象没有被正确地释放或者没有及时地取消注册,可能会导致主题对象保持对观察者对象的引用,从而导致内存泄漏

  2. 可能引起循环引用

    如果观察者对象之间存在循环引用,可能会导致系统出现死锁或者其他不可预期的问题

  3. 可能导致性能问题

    当观察者对象过多或者观察者对象的通知频率过高时,可能会导致系统的性能下降

  4. 可能导致事件顺序问题

    观察者模式通常是异步通知观察者对象的,因此可能会导致观察者对象接收到通知的顺序与注册的顺序不一致的问题


实现方式

创建基础的观察者类,并冻结它

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49

/**
* 观察者类,提供订阅、取消订阅和通知功能。
*/
class Observer {
/**
* 构造函数初始化观察者对象。
*/
constructor() {
this.observers = new Set() // 使用Set存储订阅的函数
}

/**
* 订阅功能,添加一个观察者函数。
* @param {Function} fn - 需要订阅的函数。
* @throws {Error} 如果传入的参数不是函数类型,则抛出错误。
*/
subscribe(fn) {
if (typeof fn !== 'function') {
throw new Error('Observer.subscribe: Expected function')
}
this.observers.add(fn)
}

/**
* 取消订阅功能,移除一个观察者函数。
* @param {Function} fn - 需要取消订阅的函数。
* @throws {Error} 如果传入的参数不是函数类型,则抛出错误。
*/
unsubscribe(fn) {
if (typeof fn !== 'function') {
throw new Error('Observer.unsubscribe: Expected function')
}
this.observers.delete(fn)
}

/**
* 通知所有订阅者函数。
* @param {*} data - 传递给订阅者函数的数据。
*/
notify(data) {
this.observers.forEach(observer => observer(data)) // 循环调用所有订阅者函数,传入数据
}
}

// 创建一个全局不变的观察者实例
const Watcher = Object.freeze(new Observer())

export default Watcher


导入使用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30

/**
* 导入Watcher对象,该对象来自于ObserverPattern模块,用于实现观察者模式。
*/
import Watcher from '../ObserverPattern'

/**
* 定义第一个观察者函数,当接收到数据时,打印出数据。
* @param {any} data - 接收到的数据。
*/
const observer1 = data => console.log(`Observer 1 received: ${data}`)

/**
* 定义第二个观察者函数,当接收到数据时,打印出数据。
* @param {any} data - 接收到的数据。
*/
const observer2 = data => console.log(`Observer 2 received: ${data}`)

// 订阅observer1和observer2,使其成为Watcher的观察者。
Watcher.subscribe(observer1)
Watcher.subscribe(observer2)

// 通知所有观察者,发送消息'Hello World'。
Watcher.notify('Hello World')

// 取消observer1的订阅。
Watcher.unsubscribe(observer1)

// 再次通知所有观察者,发送消息'How are you?',此时observer1不会收到该消息。
Watcher.notify('How are you?')


场景

  1. 表单字段的验证与提示

    当用户在表单中输入内容时,需要实时验证输入的有效性,并及时提示用户

    这里可以将表单字段作为观察者,输入事件作为主题,当字段的值发生变化时,触发相应的验证函数来更新提示信息

  2. 组件间通信

    在复杂的前端应用中,不同的组件之间可能需要进行通信和数据传递

    观察者模式可以被用来实现组件间的解耦,一个组件可以作为主题,而其他组件则可以订阅主题来获取相关的数据或状态更新

  3. 全局状态管理

    在大型前端应用中,可能会存在一些全局的状态需要在不同的组件中共享和管理

    观察者模式可以被用来实现全局状态的管理,一个状态对象可以作为主题,而其他组件则可以订阅该主题来获取状态的更新

  4. 路由管理

    在单页面应用(SPA)中,路由管理是一个重要的功能

    当路由发生变化时,不同的页面或组件需要做出相应的响应

    观察者模式可以被用来实现路由管理,路由对象可以作为主题,而页面或组件则可以订阅该主题来获取路由的变化

  5. 事件总线

    在前端开发中,可能会遇到需要在不同组件之间进行事件通信的情况

    观察者模式可以被用来实现事件总线,一个事件总线对象可以作为主题,而其他组件则可以订阅事件总线来接收和处理事件


源代码