这一系列是对平时工做与学习中应用到的设计模式的梳理与总结。
因为关于设计模式的定义以及相关介绍的文章已经不少,因此不会过多的涉及。该系列主要内容是来源于实际场景的示例。
定义描述主要来自 head first design pattern,UML 图来源。javascript
defines a one-to-many dependency between objects so that when one object changes state, all of its dependents are notified and updated automatically. — head first design pattern
「观察者模式」定义了对象之间一对多的依赖关系,当一个对象状态改变时,它的全部依赖都会被通知而且自动更新。html
观察者模式的类图以下:java
在该类图中,咱们看到四个角色:typescript
通常来讲,目标自己具备数据,观察者会观察目标数据的变化,说是观察者观察,实际上是目标在变化时通知它的全部观察者 “我变化了”。设计模式
咱们想要构造一个对象,当这个对象的值改动时都将会通知。在javascript 中如何知道一个对象或者一个属性是否更新了呢?咱们有几个选项:函数
一个显式调用的 set API 基本上就是观察者模式的模版代码了,虽然它看起来很不智能(React:说我吗?),但实现成本确实很低。学习
class Subject<T extends object> { private state: T private observers: Observer<this>[] = [] constructor (state: T) { this.state = state } setState (state: Partial<T>) { Object.assign(this.state, state) this.notify() } getState () { return this.state } attach (observer: Observer<this>) { this.observers.push(observer) } notify () { this.observers.forEach(observer => observer.update(this)) } } class Observer< T extends Subject<any>, K extends (subject: T) => unknown = (subject: T) => unknown, > { private cb: K constructor (cb: K) { this.cb = cb } update (subject: T) { this.cb(subject) } } const data = { a: 1, b: 2 } const subject = new Subject(data) const observerA = new Observer<typeof subject>(subject => { console.log('obA', subject.getState().a) }) const observerB = new Observer<typeof subject>(subject => { console.log('obB', subject.getState().a) }) subject.attach(observerA) subject.attach(observerB) subject.setState({ a: 10 }) // 输出 "obA 10" // 输出 "obB 10"
固然,光是这样是不够的,咱们后续还须要作 Diff 才能知道属性值是否有变化,若是没有变化的话就不须要 notify
,这里就再也不赘述。setState
这种调用显然没有直接改属性来的舒服,因此让咱们用 Proxy 稍微改造一下。this
class Subject<T extends object> { state: T private observers: Observer<this>[] = [] constructor (state: T) { this.state = new Proxy(state, { get(target, key: keyof T) { return Reflect.get(target, key) }, set(target, key: keyof T, val) { Reflect.set(target, key, val) this.notify(key, val) // added return true } }) } attach (observer: Observer<this>) { this.observers.push(observer) } notify () { this.observers.forEach(observer => observer.update(this)) } } class Observer< T extends Subject<any>, K extends (subject: T) => unknown = (subject: T) => unknown, > { private cb: K constructor (cb: K) { this.cb = cb } update (subject: T) { this.cb(subject) } } const data = { a: 1, b: 2 } const subject = new Subject(data) const observerA = new Observer<typeof subject>(subject => { console.log('obA', subject.state.a) }) const observerB = new Observer<typeof subject>(subject => { console.log('obB', subject.state.a) }) subject.attach(observerA) subject.attach(observerB) subject.state.a = 10 // 输出 "obA 10" // 输出 "obB 10"
看起来不错,咱们已经完成了咱们想要的,固然,这只是一个简单的例子,还不支持多层对象结构,不过这不是本文的重点。可是在某些状况下,咱们只想监听 “相关” 的属性,这个需求须要如何实现呢?其实也很简单。spa
class Subject<T extends object> { state: T private observersMap: Map< keyof T, Set<Observer<any>> > = new Map() private keys: (keyof T)[] = [] constructor (state: T) { this.state = new Proxy(state, { get: (target, key: keyof T) => { this.keys.push(key) // added return Reflect.get(target, key) }, set: (target, key: keyof T, val) => { Reflect.set(target, key, val) this.notify(key, val) return true } }) } attach (observer: Observer<this>) { observer.run(this) this.keys.forEach((key) => { let observers = this.observersMap.get(key) if(!observers) { observers = new Set() this.observersMap.set(key, observers) } observers.add(observer) }) this.keys = [] } notify (key: keyof T, val: T[keyof T]) { const observers = this.observersMap.get(key) if(observers) { observers.forEach(observer => observer.update(val)) } } } class Observer< T extends Subject<any>, K extends (subject: T) => unknown = (subject: T) => unknown, F extends (val: T[keyof T]) => unknown = (val: T[keyof T]) => unknown > { private func: K private cb: F constructor (func: K, cb: F) { this.func = func this.cb = cb } run(subject: T) { this.func(subject) } update (val: T[keyof T]) { this.cb(val) } } const data = { a: 1, b: 2 } const subject = new Subject(data) const observerA = new Observer<typeof subject>( (subject) => { console.log('prop a should log when changed', subject.state.a) }, (val) => { console.log('a changed', val) } ) subject.attach(observerA) subject.state.a = 10 // 输出 "a changed 10" subject.state.b = 10 // 没有输出 const observerB = new Observer<typeof subject>( (subject) => { console.log('prop a should log when changed', subject.state.b) }, (val) => { console.log('b changed', val) } ) subject.attach(observerB) subject.state.b = 100 // 输出 "b changed 10"
通过改造事后,只有在 func
里用到的属性才会响应修改了。若是咱们将一个 render
函数看成 func
和 cb
传入,那就搭建起了数据层(Model)到视图层(View)的桥梁,当数据变化时,那么 DOM 就会响应变化而且更新。设计
const data = { a: 1, b: 2 } const subject = new Subject(data) const render = <T extends typeof subject>(subject: T) => { document.body.innerText = subject.state.a.toString() } const observerA = new Observer<typeof subject>( (subject) => { render(subject) }, () => { render(subject) } )
事实上,观察者模式又叫发布订阅模式。可是在实践中,它们对应着不一样的设计,通常来讲,发布订阅会在 Subject 与 Observer 之间增长一层中介来处理二者之间的耦合与沟通。不过本质上来讲他俩没有区别。
咱们经常用在组件间的通讯时的事件总线就是一个典型的发布订阅模式。
class EventBus { private events: { [key: string]: [Function]; } = {} on (eventName: string, cb: Function) { this.events[eventName] = this.events[eventName] || [] this.events[eventName].push(cb) } off (eventName: string, cb: Function) { const index = this.events[eventName].indexOf(cb) this.events[eventName].splice(index, 1) } emit (eventName: string, data?: unknown) { const cbs = this.events[eventName] if (cbs) { cbs.forEach(cb => cb(data)) } } } const eventBus = new EventBus() eventBus.on('testA', console.log) eventBus.on('testB', console.log) eventBus.emit('testA', 1) // 输出 1
经过以上几个例子,咱们能够看出观察者有一下几个特色: