观察者模式
允许你定义一种一对多的订阅关系:
当一个对象(被观察者 / Subject)的状态发生变化时,
所有依赖于它的对象(观察者 / Observer)都会收到通知并自动更新。
维护观察者列表
被观察者内部维护一个 Observer[]
-
支持:
-
添加观察者
-
移除观察者
定义订阅机制
观察者通过 subscribe / attach 注册
-
被观察者不关心观察者的具体实现,只关心“它们实现了统一接口”
-
观察者 只响应通知,不相互通信
事件触发时统一通知
- 状态变化 / 新事件发生
- 被观察者遍历观察者列表
- 逐个调用观察者的
update方法
例如:
1.vue的响应式系统
2.DOM 事件系统和 MutationObserver 都是观察者模式的典型实现:
DOM 元素或被监听对象作为 Subject,内部维护监听器列表;
当事件或状态变化发生时,统一通知所有观察者。
/**
* 观察者模式
*
* 特点:
* - 一对多订阅关系
* - 被观察者内部维护观察者列表
* - 支持订阅 / 取消订阅
* - 状态变化时统一通知
* - 观察者只需遵循统一的 update 约定
*/
/**
* 创建一个被观察者(Subject)
*/
function createSubject() {
// 维护观察者列表 observers 是一个数组 数组里每一项 必须是一个函数(接收 state 参数、没有返回值的函数)
const observers: Array<(state: any) => void> = []
// 被观察者的内部状态
let state: any = null
/**
* 订阅(添加观察者)
*/
function subscribe(observer: (state: any) => void) {
observers.push(observer)
}
/**
* 取消订阅(移除观察者)
*/
function unsubscribe(observer: (state: any) => void) {
const index = observers.indexOf(observer)
if (index !== -1) {
observers.splice(index, 1)
}
}
/**
* 状态更新
* 状态变化是“事件源”
*/
function setState(newState: any) {
state = newState
notify()
}
/**
* 统一通知所有观察者
*/
function notify() {
observers.forEach(observer => {
observer(state)
})
}
// 只暴露必要能力(封装内部实现)
return {
subscribe,
unsubscribe,
setState
}
}
/**
* ===============================
* 使用示例
* ===============================
*/
// 创建被观察者
const subject = createSubject()
// 创建观察者(函数)
const observerA = (state: any) => {
console.log('观察者 A 收到更新:', state)
}
const observerB = (state: any) => {
console.log('观察者 B 收到更新:', state)
}
// 订阅
subject.subscribe(observerA)
subject.subscribe(observerB)
// 状态变化 → 通知所有观察者
subject.setState({ count: 1 })
// 取消订阅 A
subject.unsubscribe(observerA)
// 再次状态变化
subject.setState({ count: 2 })
/**
* 输出:
* 观察者 A 收到更新: { count: 1 }
* 观察者 B 收到更新: { count: 1 }
* 观察者 B 收到更新: { count: 2 }
*/
发布订阅模式
观察者模式中,观察者需要直接订阅被观察者,被观察者内部维护观察者列表,并在状态变化时直接通知观察者;
而发布–订阅模式通过引入中间消息代理,使发布者与订阅者不直接通信,双方只依赖消息中心,从而实现更彻底的解耦。
观察者模式
- DOM
addEventListener MutationObserver- Vue 响应式(dep ↔ watcher)
发布–订阅模式
- EventBus
mitt库- Node.js
EventEmitter - 消息队列(Kafka / RabbitMQ)
/**
* 发布–订阅模式(Publish–Subscribe)
*
* 核心特点:
* - 引入“消息中心 / 事件总线(Event Bus)”作为中间代理
* - 发布者和订阅者完全解耦
* - 发布者不关心谁订阅
* - 订阅者不关心谁发布
* - 通过“事件名 / topic”进行通信
*/
/**
* 创建一个事件总线(消息中介)
*/
function createEventBus() {
/**
* events 是一个 Map:
* key -> 事件名(string)
* value -> 订阅该事件的回调函数数组(数组里每一项 必须是一个函数(接收 data 参数、没有返回值的函数))
*/
const events: Map<string, Array<(data: any) => void>> = new Map()
/**
* 订阅事件
* @param eventName 事件名 / topic
* @param handler 回调函数(订阅者)
*/
function on(eventName: string, handler: (data: any) => void) {
// 如果该事件还没有订阅者,先初始化一个数组
if (!events.has(eventName)) {
events.set(eventName, [])
}
// 将订阅者加入事件对应的列表
// !非空断言 前面已经判断是否存在该订阅者 不存在则初始化一个数组
events.get(eventName)!.push(handler)
}
/**
* 取消订阅
*/
function off(eventName: string, handler: (data: any) => void) {
const handlers = events.get(eventName)
if (!handlers) return
const index = handlers.indexOf(handler)
if (index !== -1) {
handlers.splice(index, 1)
}
}
/**
* 发布事件
* @param eventName 事件名
* @param data 事件数据
*/
function emit(eventName: string, data: any) {
const handlers = events.get(eventName)
if (!handlers) return
// 通知所有订阅该事件的订阅者
handlers.forEach(handler => {
handler(data)
})
}
return {
on,
off,
emit
}
}
/**
* 使用示例
*/
// 创建一个全局事件总线(中介)
const eventBus = createEventBus()
/**
* 订阅者 A
* 只关心 "order:create" 这个事件
*/
const subscriberA = (data: any) => {
console.log('订阅者 A 收到订单创建事件:', data)
}
/**
* 订阅者 B
* 也订阅同一个事件
*/
const subscriberB = (data: any) => {
console.log('订阅者 B 收到订单创建事件:', data)
}
// 订阅事件
eventBus.on('order:create', subscriberA)
eventBus.on('order:create', subscriberB)
/**
* 发布者
* 发布事件时:
* - 不知道谁在监听
* - 只负责发消息
*/
eventBus.emit('order:create', {
orderId: 1001,
price: 299
})
// 取消订阅 A
eventBus.off('order:create', subscriberA)
// 再次发布事件
eventBus.emit('order:create', {
orderId: 1002,
price: 499
})
/**
* 输出:
* 订阅者 A 收到订单创建事件: { orderId: 1001, price: 299 }
* 订阅者 B 收到订单创建事件: { orderId: 1001, price: 299 }
* 订阅者 B 收到订单创建事件: { orderId: 1002, price: 499 }
*/
模版模式
在抽象父类中定义一个算法的整体框架(执行步骤及顺序),
将其中部分可变的步骤延迟到子类实现。
子类在不改变算法结构的前提下,重写特定步骤,从而实现不同行为。
抽象父类(Template / Abstract Class)
- 定义算法的整体流程(模板方法)
- 实现通用逻辑
- 声明需要子类实现的抽象步骤(或 Hook)
具体子类(Concrete Class)
- 继承父类,复用算法结构
- 针对流程中的某些步骤进行重写
/**
* 父类中的模板方法
* - update 是“模板方法” render / shouldUpdate → 抽象步骤 commit / didUpdate → 可选步骤
* - 流程固定
* - 各步骤通过函数参数传入(策略 / hook)
*/
function createUpdater(options: {
shouldUpdate?: (prevState: any, nextState: any) => boolean
render: (state: any) => any
commit?: (vdom: any) => void
didUpdate?: () => void
}) {
// 内部状态(闭包)
let state: any = null
/**
* ===== 模板方法(流程不可变)=====
*/
function update(nextState: any) {
const prevState = state
// 1. 是否更新(可选 hook)
if (
options.shouldUpdate &&
!options.shouldUpdate(prevState, nextState)
) {
return
}
// 2. 更新状态
state = nextState
// 3. render(必须)
const vdom = options.render(state)
// 4. commit(可选 hook) ?. 如果前面的值存在(不是 null / undefined),才继续访问或执行
options.commit?.(vdom)
// 5. 更新完成(可选 hook)
options.didUpdate?.()
}
return {
update
}
}
/**
* 子类中的具体实现
*/
const counter = createUpdater({
shouldUpdate(prev, next) {
console.log('shouldUpdate')
return next.count <= 5
},
render(state) {
console.log('render')
return `<div>count: ${state.count}</div>`
},
commit(vdom) {
console.log('commit DOM:', vdom)
},
didUpdate() {
console.log('didUpdate')
}
})
// 触发更新
counter.update({ count: 1 })
counter.update({ count: 2 })
counter.update({ count: 6 }) // 不会更新
// 输出结果
// shouldUpdate
// render
// commit DOM: <div>count: 1</div>
// didUpdate
// shouldUpdate
// render
// commit DOM: <div>count: 2</div>
// didUpdate
// shouldUpdate false -> 不更新 模板方法提前终止,后续步骤(状态更新、render、commit、didUpdate)均不会执行。
策略模式
定义一系列算法,将每一个算法封装起来,使它们可以相互替换。
策略模式使算法的变化独立于使用算法的客户端。
模板方法:固定流程
策略模式:可替换算法
/**
* 场景:i18n 根据语言切换文案
*
* 思想:
* - 不同语言 = 不同算法(策略)
* - 运行时根据 locale 选择策略
* - 使用方只依赖统一接口,不关心具体实现
*/
/**
* 这里用函数类型来表示“策略”
*/
type I18nStrategy = (key: string) => string
type Locale = 'zh' | 'en'
/**
* 中文翻译策略
*/
const zhStrategy: I18nStrategy = (key) => {
// 中文词典
const dict: Record<string, string> = {
hello: '你好',
world: '世界'
}
// 如果找不到对应 key,则兜底返回 key 本身
return dict[key] ?? key
}
/**
* 英文翻译策略
*/
const enStrategy: I18nStrategy = (key) => {
// 英文词典
const dict: Record<string, string> = {
hello: 'Hello',
world: 'World'
}
return dict[key] ?? key
}
/**
* 策略注册表
*
* key:策略标识(locale)
* value:具体策略实现
*/
const strategyMap: Record<string, I18nStrategy> = {
zh: zhStrategy,
en: enStrategy
}
/**
* 上下文
*
* Context 持有一个策略
* 但不关心策略的具体实现
* 只通过统一接口(I18nStrategy)调用
*/
function createI18n(locale: Locale) {
const strategy = strategyMap[locale]
if (!strategy) {
throw new Error(`Unsupported locale: ${locale}`)
}
/**
* 对外暴露的统一 API
*/
function t(key: string): string {
return strategy(key)
}
return { t }
}
/**
* 使用示例(Client)
*/
// 使用中文策略
const i18nZh = createI18n('zh')
console.log(i18nZh.t('hello')) // 你好
console.log(i18nZh.t('world')) // 世界
// 使用英文策略
const i18nEn = createI18n('en')
console.log(i18nEn.t('hello')) // Hello
console.log(i18nEn.t('world')) // World
迭代器模式
在不暴露集合内部表示的前提下,
提供一种统一的方式顺序访问集合中的元素。
JS 中,Map 天然支持用 for...of 进行迭代
/**
* 迭代器模式(Iterator Pattern)
*
* 目标:
* - 在不暴露集合内部结构的前提下
* - 提供一种统一的方式顺序访问集合元素
*
*/
/**
* 迭代器接口(Iterator Protocol)
*/
interface MyIterator<T> {
next(): {
value: T | undefined
done: boolean
}
}
/**
* 创建一个“可迭代的集合”
*
* - 内部使用数组存储数据(对外不可见)
* - 通过闭包封装内部状态
* - 通过 [Symbol.iterator] 暴露遍历能力
*/
function createNumberCollection() {
// 私有数据(外部无法直接访问)
const items: number[] = []
/**
* 添加元素
*/
function add(value: number) {
items.push(value)
}
/**
* 迭代器工厂函数
*
* 每次调用都会创建一个新的迭代器实例
* 用于保存独立的遍历状态
*/
function createIterator(): MyIterator<number> {
let index = 0
return {
next() {
if (index < items.length) {
return {
value: items[index++],
done: false
}
}
return {
value: undefined,
done: true
}
}
}
}
/**
* 对外暴露的集合 API
*/
return {
add,
/**
* Iterable Protocol
*
* - 定义 [Symbol.iterator] 方法
* - [Symbol.iterator] 是一个 key, 它对应的 value 必须是一个函数, 这个函数在遍历开始时被 JS 引擎调用, 并返回一个带 next 方法的迭代器对象。
*
* 一旦实现该方法,该对象即可被:
* - for...of
* - Array.from
* - 展开运算符 (...)
* 等语言结构遍历
*/
[Symbol.iterator]: createIterator
}
}
/**
* ================================
* 使用示例
* ================================
*/
const collection = createNumberCollection()
collection.add(1)
collection.add(2)
collection.add(3)
// for...of 会自动:
// 1. 调用 collection[Symbol.iterator]()
// 2. 不断执行 next()
// 3. 直到 done === true
for (const num of collection) {
console.log(num)
}
// 输出:
// 1
// 2
// 3