设计模式之观察者模式
观察者模式的定义
观察者模式定义了一种一对多的依赖关系,让多个观察者对象同时监听某一个目标对象,当这个目标对象的状态发生变化时,会通知所有观察者对象,使它们能够自动更新。 —— Graphic Design Patterns
现实生活中的观察者模式
最近领导拉了个群,群名字叫XXXX项目奋斗群,项目组的开发测试人员都被拉进去了,群里的小伙伴们都情绪激动,热情积极等着新项目的到来,然而领导说目前项目方案还没下来,大家都先别激动,我只是提前拉个群而已。但是大家都做好了下一周开始奋斗的(996)的觉悟了,所以每天都在干着急的等呀等呀,终于再周三的时候,项目经理抛出了xxxxx项目.zip包,并@李xx,王xx,吴xx准备干活,然后李xx,王xx,吴xx同学开始准备工作了,这个就是观察者模式(发布 - 订阅模式)。在上述的过程中,需求文档(目标对象)的发布者只有一个—领导。而需求信息的接受者却有多个——前端、后端、测试同学,这些同学的共性就是他们需要根据需求信息开展自己后续的工作、因此都非常关心这个需求信息,于是不得不时刻关注着这个群的群消息提醒,他们是实打实的订阅者,即观察者对象。
代码模拟
我们暂且将发布者定义为Publisher类,有发布消息添加订阅者,删除订阅者和发布订阅消息这三个功能
// 定义发布者类
class Publisher {
constructor() {
this.observers = []
console.log('Publisher created')
}
// 增加订阅者
add(observer) {
console.log('Publisher.add invoked')
this.observers.push(observer)
}
// 移除订阅者
remove(observer) {
console.log('Publisher.remove invoked')
this.observers.forEach((item, i) => {
if (item === observer) {
this.observers.splice(i, 1)
}
})
}
// 通知所有订阅者
notify() {
console.log('Publisher.notify invoked')
this.observers.forEach((observer) => {
observer.update(this)
})
}
}
订阅者的功能其实很简单,它的行为只有两个——被通知、去执行(本质上是接受发布者的调用,这步我们在Publisher中已经做掉了)。既然我们在Publisher中做的是方法调用,那么我们在订阅者类里要做的就是方法的定义:
class Observer {
constructor(){
console.log('hello i\'m Observer')
}
update() {
console.log('Observer.update invoked')
}
}
以上,我们就完成了最基本的发布者和订阅者类的设计和编写。在实际的业务开发中,我们所有的定制化的发布者/订阅者逻辑都可以基于这两个基本类来改写。比如我们可以通过拓展发布者类,来使所有的订阅者来监听某个特定状态的变化。仍然以开篇的例子为例,我们让开发者们来监听需求文档(prd)的变化:
// 定义一个具体的需求文档(prd)发布类
class PrdPublisher extends Publisher {
constructor() {
super()
// 初始化需求文档
this.prdState = null
// 韩梅梅还没有拉群,开发群目前为空
this.observers = []
console.log('PrdPublisher created')
}
// 该方法用于获取当前的prdState
getState() {
console.log('PrdPublisher.getState invoked')
return this.prdState
}
// 该方法用于改变prdState的值
setState(state) {
console.log('PrdPublisher.setState invoked')
// prd的值发生改变
this.prdState = state
// 需求文档变更,立刻通知所有开发者
this.notify()
}
}
作为订阅方,开发者的任务也变得具体起来:接收需求文档、并开始干活:
class DeveloperObserver extends Observer {
constructor() {
super()
// 需求文档一开始还不存在,prd初始为空对象
this.prdState = {}
console.log('DeveloperObserver created')
}
// 重写一个具体的update方法
update(publisher) {
console.log('DeveloperObserver.update invoked')
// 更新需求文档
this.prdState = publisher.getState()
// 调用工作函数
this.work()
}
// work方法,一个专门搬砖的方法
work() {
// 获取需求文档
const prd = this.prdState
// 开始基于需求文档提供的信息搬砖。。。
...
console.log('996 begins...')
}
}
vue 对发布订阅模式的使用
我们都知道 Vue 有个最显著的特性,便是侵入性不是很强的响应式系统。这个特性就是对发布订阅模式非常好的应用。我们接下来就来看看这个特性是怎么应用的。
vue 的数据初始化:
var v = new Vue({
data() {
return {
a: 'hello'
}
}
})
这个初始化的代码的背后包含着发布订阅模式的思想,接下来看看官网的一个图
接下来就是网友的一个图:@xuqiang521
- 数据劫持
从上图可以看到,数据劫持的核心方法就是使用Object.defineProperty把属性转化成getter/setter。(因为这个是 ES5 中的方法,所以这也是 Vue 不支持 ie8 及以下浏览器的原因之一。)在数据传递变更的时候,会进入到我们封装的Dep和Watcher中进行处理。
1.1 遍历劫持
数据不紧紧是基本类型的数据,也有可能是对象或者数组。基本类型的数据和对象的处理起来比较简单。
walk(obj) {
const keys = Object.keys(obj)
for (let i = 0; i < keys.length; ++i) {
defineReactive(obj, keys[i], obj[keys[i]])
}
}
核心的劫持相关函数以及属性的订阅和发布:
/**
* Define a reactive property on an Object.
*/
export function defineReactive(
obj: Object,
key: string,
val: any,
customSetter?: Function
) {
/*在闭包中定义一个dep对象*/
const dep = new Dep()
const property = Object.getOwnPropertyDescriptor(obj, key)
if (property && property.configurable === false) {
return
}
/*如果之前该对象已经预设了getter以及setter函数则将其取出来,新定义的getter/setter中会将其执行,保证不会覆盖之前已经定义的getter/setter。*/
// cater for pre-defined getter/setters
const getter = property && property.get
const setter = property && property.set
/*对象的子对象递归进行observe并返回子节点的Observer对象*/
let childOb = observe(val)
Object.defineProperty(obj, key, {
enumerable: true,
configurable: true,
get: function reactiveGetter() {
/*如果原本对象拥有getter方法则执行*/
const value = getter ? getter.call(obj) : val
if (Dep.target) {
/*进行依赖收集*/
dep.depend()
if (childOb) {
/*子对象进行依赖收集,其实就是将同一个watcher观察者实例放进了两个depend中,一个是正在本身闭包中的depend,另一个是子元素的depend*/
childOb.dep.depend()
}
if (Array.isArray(value)) {
/*是数组则需要对每一个成员都进行依赖收集,如果数组的成员还是数组,则递归。*/
dependArray(value)
}
}
return value
},
set: function reactiveSetter(newVal) {
/*通过getter方法获取当前值,与新值进行比较,一致则不需要执行下面的操作*/
const value = getter ? getter.call(obj) : val
/* eslint-disable no-self-compare */
if (newVal === value || (newVal !== newVal && value !== value)) {
return
}
/* eslint-enable no-self-compare */
if (process.env.NODE_ENV !== 'production' && customSetter) {
customSetter()
}
if (setter) {
/*如果原本对象拥有setter方法则执行setter*/
setter.call(obj, newVal)
} else {
val = newVal
}
/*新的值需要重新进行observe,保证数据响应式*/
childOb = observe(newVal)
/*dep对象通知所有的观察者*/
dep.notify()
}
})
}
最开始在初始化的时候是对 data 里面的数据就开始劫持监听了。初始化的时候就调用了observe方法
/**
* Attempt to create an observer instance for a value,
* returns the new observer if successfully observed,
* or the existing observer if the value already has one.
*/
/*
尝试创建一个Observer实例(__ob__),如果成功创建Observer实例则返回新的Observer实例,如果已有Observer实例则返回现有的Observer实例。
*/
export function observe(value: any, asRootData: ?boolean): Observer | void {
/*判断是否是一个对象*/
if (!isObject(value)) {
return
}
let ob: Observer | void
/*这里用__ob__这个属性来判断是否已经有Observer实例,如果没有Observer实例则会新建一个Observer实例并赋值给__ob__这个属性,如果已有Observer实例则直接返回该Observer实例*/
if (hasOwn(value, '__ob__') && value.__ob__ instanceof Observer) {
ob = value.__ob__
} else if (
/*这里的判断是为了确保value是单纯的对象,而不是函数或者是Regexp等情况。*/
observerState.shouldConvert &&
!isServerRendering() &&
(Array.isArray(value) || isPlainObject(value)) &&
Object.isExtensible(value) &&
!value._isVue
) {
ob = new Observer(value)
}
if (asRootData && ob) {
/*如果是根数据则计数,后面Observer中的observe的asRootData非true*/
ob.vmCount++
}
return ob
}
1.2 返回值
上面的数据observe之后返回的就是一个 Observer 的实例
ob = new Observer(value)
return ob
2."中转站"
在第一步数据劫持的时候,数据的获取或者修改的时候,都会做出对应的操作。这些操作的目的很简单,就是“通知”到“中转站”。这个“中转站”主要就是对数据的变更起通知作用以及存放依赖这些数据的“地方”。
这个"中转站"就是由"Dep"和“Watcher” 类构成的。每个被劫持的数据都会产生一个这样的“中转站”
2.1 Dep
Dep,全名 Dependency,从名字我们也能大概看出 Dep 类是用来做依赖收集的,但是也有通知对应的订阅者的作用 ,让它执行自己的操作,具体怎么收集呢?
/**
* A dep is an observable that can have multiple
* directives subscribing to it.
*/
export default class Dep {
static target: ?Watcher
id: number
subs: Array<Watcher>
constructor() {
this.id = uid++
this.subs = []
}
/*添加一个观察者对象*/
addSub(sub: Watcher) {
this.subs.push(sub)
}
/*移除一个观察者对象*/
removeSub(sub: Watcher) {
remove(this.subs, sub)
}
/*依赖收集,当存在Dep.target的时候添加观察者对象*/
depend() {
if (Dep.target) {
Dep.target.addDep(this)
}
}
/*通知所有订阅者*/
notify() {
// stabilize the subscriber list first
const subs = this.subs.slice()
for (let i = 0, l = subs.length; i < l; i++) {
subs[i].update()
}
}
}
// the current target watcher being evaluated.
// this is globally unique because there could be only one
// watcher being evaluated at any time.
Dep.target = null
/*依赖收集完需要将Dep.target设为null,防止后面重复添加依赖。*/
const targetStack = []
export function pushTarget(_target: Watcher) {
if (Dep.target) targetStack.push(Dep.target)
// 改变目标指向
Dep.target = _target
}
export function popTarget() {
// 删除当前目标,重算指向
Dep.target = targetStack.pop()
}
代码很简短,但它做的事情却很重要
定义 subs 数组,用来收集订阅者 Watcher
当劫持到数据变更的时候,通知订阅者 Watcher 进行 update 操作
2.2 Watcher
Watcher 就是订阅者(观察者)。 主要的作用就是就是订阅 Dep(每个属性都会有一个 dep),当 Dep 发出消息传递(notify)的时候,所以订阅着 Dep 的 Watchers 会进行自己的 update 操作。
export default class Watcher {
vm: Component
expression: string
cb: Function
id: number
deep: boolean
user: boolean
lazy: boolean
sync: boolean
dirty: boolean
active: boolean
deps: Array<Dep>
newDeps: Array<Dep>
depIds: ISet
newDepIds: ISet
getter: Function
value: any
constructor(
vm: Component,
expOrFn: string | Function,
cb: Function,
options?: Object
) {
this.vm = vm
/*_watchers存放订阅者实例*/
vm._watchers.push(this)
// options
if (options) {
this.deep = !!options.deep
this.user = !!options.user
this.lazy = !!options.lazy
this.sync = !!options.sync
} else {
this.deep = this.user = this.lazy = this.sync = false
}
this.cb = cb
this.id = ++uid // uid for batching
this.active = true
this.dirty = this.lazy // for lazy watchers
this.deps = []
this.newDeps = []
this.depIds = new Set()
this.newDepIds = new Set()
this.expression =
process.env.NODE_ENV !== 'production' ? expOrFn.toString() : ''
// parse expression for getter
/*把表达式expOrFn解析成getter*/
if (typeof expOrFn === 'function') {
this.getter = expOrFn
} else {
this.getter = parsePath(expOrFn)
if (!this.getter) {
this.getter = function() {}
process.env.NODE_ENV !== 'production' &&
warn(
`Failed watching path: "${expOrFn}" ` +
'Watcher only accepts simple dot-delimited paths. ' +
'For full control, use a function instead.',
vm
)
}
}
this.value = this.lazy ? undefined : this.get()
}
/**
* Evaluate the getter, and re-collect dependencies.
*/
/*获得getter的值并且重新进行依赖收集*/
get() {
/*将自身watcher观察者实例设置给Dep.target,用以依赖收集。*/
pushTarget(this)
let value
const vm = this.vm
/*
执行了getter操作,看似执行了渲染操作,其实是执行了依赖收集。
在将Dep.target设置为自生观察者实例以后,执行getter操作。
譬如说现在的的data中可能有a、b、c三个数据,getter渲染需要依赖a跟c,
那么在执行getter的时候就会触发a跟c两个数据的getter函数,
在getter函数中即可判断Dep.target是否存在然后完成依赖收集,
将该观察者对象放入闭包中的Dep的subs中去。
*/
if (this.user) {
try {
value = this.getter.call(vm, vm)
} catch (e) {
handleError(e, vm, `getter for watcher "${this.expression}"`)
}
} else {
value = this.getter.call(vm, vm)
}
// "touch" every property so they are all tracked as
// dependencies for deep watching
/*如果存在deep,则触发每个深层对象的依赖,追踪其变化*/
if (this.deep) {
/*递归每一个对象或者数组,触发它们的getter,使得对象或数组的每一个成员都被依赖收集,形成一个“深(deep)”依赖关系*/
traverse(value)
}
/*将观察者实例从target栈中取出并设置给Dep.target*/
popTarget()
this.cleanupDeps()
return value
}
/**
* Add a dependency to this directive.
*/
/*添加一个依赖关系到Deps集合中*/
addDep(dep: Dep) {
const id = dep.id
if (!this.newDepIds.has(id)) {
this.newDepIds.add(id)
this.newDeps.push(dep)
if (!this.depIds.has(id)) {
dep.addSub(this)
}
}
}
/**
* Clean up for dependency collection.
*/
/*清理依赖收集*/
cleanupDeps() {
/*移除所有观察者对象*/
let i = this.deps.length
while (i--) {
const dep = this.deps[i]
if (!this.newDepIds.has(dep.id)) {
dep.removeSub(this)
}
}
let tmp = this.depIds
this.depIds = this.newDepIds
this.newDepIds = tmp
this.newDepIds.clear()
tmp = this.deps
this.deps = this.newDeps
this.newDeps = tmp
this.newDeps.length = 0
}
/**
* Subscriber interface.
* Will be called when a dependency changes.
*/
/*
调度者接口,当依赖发生改变的时候进行回调。
*/
update() {
/* istanbul ignore else */
if (this.lazy) {
this.dirty = true
} else if (this.sync) {
/*同步则执行run直接渲染视图*/
this.run()
} else {
/*异步推送到观察者队列中,由调度者调用。*/
queueWatcher(this)
}
}
/**
* Scheduler job interface.
* Will be called by the scheduler.
*/
/*
调度者工作接口,将被调度者回调。
*/
run() {
if (this.active) {
const value = this.get()
if (
value !== this.value ||
// Deep watchers and watchers on Object/Arrays should fire even
// when the value is the same, because the value may
// have mutated.
/*
即便值相同,拥有Deep属性的观察者以及在对象/数组上的观察者应该被触发更新,因为它们的值可能发生改变。
*/
isObject(value) ||
this.deep
) {
// set new value
const oldValue = this.value
/*设置新的值*/
this.value = value
/*触发回调渲染视图*/
if (this.user) {
try {
this.cb.call(this.vm, value, oldValue)
} catch (e) {
handleError(e, this.vm, `callback for watcher "${this.expression}"`)
}
} else {
this.cb.call(this.vm, value, oldValue)
}
}
}
}
/**
* Evaluate the value of the watcher.
* This only gets called for lazy watchers.
*/
/*获取观察者的值*/
evaluate() {
this.value = this.get()
this.dirty = false
}
/**
* Depend on all deps collected by this watcher.
*/
/*收集该watcher的所有deps依赖*/
depend() {
let i = this.deps.length
while (i--) {
this.deps[i].depend()
}
}
/**
* Remove self from all dependencies' subscriber list.
*/
/*将自身从所有依赖收集订阅列表删除*/
teardown() {
if (this.active) {
// remove self from vm's watcher list
// this is a somewhat expensive operation so we skip it
// if the vm is being destroyed.
/*从vm实例的观察者列表中将自身移除,由于该操作比较耗费资源,所以如果vm实例正在被销毁则跳过该步骤。*/
if (!this.vm._isBeingDestroyed) {
remove(this.vm._watchers, this)
}
let i = this.deps.length
while (i--) {
this.deps[i].removeSub(this)
}
this.active = false
}
}
}
通过上面对 vue 的响应系统的 学习,就可以了解到这个发布订阅模式就是这样的:
Dep 负责收集所有相关的的订阅者 Watcher ,具体谁不用管,具体有多少也不用管,只需要根据 target 指向的计算去收集订阅其消息的 Watcher 即可,然后做好消息发布 notify 即可。
Watcher 负责订阅 Dep ,并在订阅的时候让 Dep 进行收集,接收到 Dep 发布的消息时,做好其 update 操作即可。