上一节说到了 Dep, Observer, Watcher 这三个对象的作用,即 Dep 是数据依赖,Observer 用来处理成员对象的增删,Watcher 负责在数据发生变动的时候执行回调函数。
这一节则主要来说:
- Watcher 何时将 Dep 添加到自己的 deps 数组中。
- Dep 何时将依赖于自身的 Watcher 添加到自身的 subs 数组中
- Observer 中的 Dep 什么时候将相关的 Watcher 添加到自身的 subs 数组中
Dep 和 Watcher 的 depend 方法及全局 Watcher 栈
全局 Watcher 栈
在 src/core/observer/dep.js 中,存在一个全局的栈,用来储存当前被触发且正在运行的 Watcher,同时 Dep 还有一个静态成员 target ,在这里可以看成是栈顶元素。
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()
}
Dep 的 depend 方法
Dep 的 depend 方法是从栈中获取栈顶的 Watcher 并添加到自己的 subs 数组中。
其中运行了 Watcher 的 addDep 来将该 Dep 添加到 Watcher 的 newDeps 数组中(之后会进行处理,就会将所有新的依赖添加到真正的 deps 数组中,这里的 newDeps 是起缓冲的作用的,而这个缓冲起了什么作用,我还有待研究),在这个 addDep 数组中又运行了 Dep 对象的 addSub 方法来将该 Watcher 添加到自己的 subs 数组中, 形成一种你中有我,我中有你的状态。
addSub (sub: Watcher) {
this.subs.push(sub)
},
...
depend () {
if (Dep.target) {
Dep.target.addDep(this)
}
}
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)
}
}
}
Watcher 的 depend 方法
Watcher 的 depend 方法是运行 deps 数组中所有的 Dep 对象的 depend 方法,比较好理解。
depend () {
let i = this.deps.length
while (i--) {
this.deps[i].depend()
}
}
全局 Watcher 栈何时执行压栈和出栈
上节中说到 Watcher 在建立的时候会接收并保存一个 getter 函数, 在 Watcher 的 get 函数中则会运行这个 getter 并获取可能的返回值赋值给 value。
在这段代码中可以看到,在执行 getter 之前,运行了 pushTarget(this) 来将自己(现在正在运行的 Watcher)压入全局 Watcher 栈,而在 getter 执行结束后运行 popTarget 执行出栈操作。
get () {
pushTarget(this) // 压栈
let value
const vm = this.vm
try {
value = this.getter.call(vm, vm)
} catch (e) {
if (this.user) {
handleError(e, vm, `getter for watcher "${this.expression}"`)
} else {
throw e
}
} finally {
// "touch" every property so they are all tracked as
// dependencies for deep watching
if (this.deep) {
traverse(value)
}
popTarget() //出栈
this.cleanupDeps()
}
return value
}
defineReactive,get 和 set 钩子函数
在 defineReactive 函数中,首先做了两个比较重要的操作:
- 新建一个 Dep 对象,通过闭包将其保存下来。
- 对该 value 执行 observe 函数,递归建立起 value 及其以下所有需要建立的 Observer 对象,同时为所有属性运行 defineReactive 函数设置钩子。
export function defineReactive (
obj: Object,
key: string,
val: any,
customSetter?: ?Function,
shallow?: boolean
) {
const dep = new Dep()
const property = Object.getOwnPropertyDescriptor(obj, key)
if (property && property.configurable === false) {
return
}
// cater for pre-defined getter/setters
const getter = property && property.get
const setter = property && property.set
let childOb = !shallow && observe(val)
......
}
接下来就是建立钩子函数。
在 get 中,主要检测全局 Watcher 栈的栈顶是否有元素,如果有则运行之前建立的 Dep 对象的 depend 来建立该依赖和 Watcher 的关联关系, 同时建立该 value 的 Observer 中的 Dep 对象和 Watcher 的关联关系(如果该 value 是对象或者数组)。 之前说过,只有在某个 Watcher 的 getter 函数中在运行中的时候(中间几个语句的执行可以忽略)才会将这个 Watcher 压栈且其出于栈顶位置,所以在这个时候如果运行了 get 钩子函数,则基本可以肯定此时运行的 getter 函数是和这个 value 有关系的,即这个依赖和栈顶的 Watcher 有关联。
在 set 中, 首先检测新值和旧值是否相等,如果相等则直接返回。之后又运行了一遍 observe 函数,因为赋的新值有可能是一个数组或者对象,即需要建立新的一个或多个 Observer。最后运行了 notify 来通知这个依赖相关联的所有 Watcher 去运行回调函数。
Object.defineProperty(obj, key, {
enumerable: true,
configurable: true,
get: function reactiveGetter () {
const value = getter ? getter.call(obj) : val
if (Dep.target) {
dep.depend()
if (childOb) {
childOb.dep.depend()
if (Array.isArray(value)) {
dependArray(value)
}
}
}
return value
},
set: function reactiveSetter (newVal) {
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.call(obj, newVal)
} else {
val = newVal
}
childOb = !shallow && observe(newVal)
dep.notify()
}
})
vm.$set 和 vm.$delete
Vue 实例对象的 $set 方法对应的是 set 方法, $delete 对应的是 del。
// 位置: src/core/instance/state.js
Vue.prototype.$set = set
Vue.prototype.$delete = del
逻辑其实很简单,就是给一个对象添加成员时先检测这个对象是不是 Vue 实例,是的话则直接返回(set 了也没用)。之后获取该对象的 Observer 并运行其中 Dep 对象的 notify 方法通知 Watcher,还要设置好新成员的钩子函数。删除对象成员时也是相似了逻辑。
// 位置: src/core/observer/index.js
export function set (target: Array<any> | Object, key: any, val: any): any {
if (Array.isArray(target) && isValidArrayIndex(key)) {
target.length = Math.max(target.length, key)
target.splice(key, 1, val)
return val
}
if (key in target && !(key in Object.prototype)) {
target[key] = val
return val
}
const ob = (target: any).__ob__
if (target._isVue || (ob && ob.vmCount)) {
process.env.NODE_ENV !== 'production' && warn(
'Avoid adding reactive properties to a Vue instance or its root $data ' +
'at runtime - declare it upfront in the data option.'
)
return val
}
if (!ob) {
target[key] = val
return val
}
defineReactive(ob.value, key, val)
ob.dep.notify()
return val
}
......
export function del (target: Array<any> | Object, key: any) {
if (Array.isArray(target) && isValidArrayIndex(key)) {
target.splice(key, 1)
return
}
const ob = (target: any).__ob__
if (target._isVue || (ob && ob.vmCount)) {
process.env.NODE_ENV !== 'production' && warn(
'Avoid deleting properties on a Vue instance or its root $data ' +
'- just set it to null.'
)
return
}
if (!hasOwn(target, key)) {
return
}
delete target[key]
if (!ob) {
return
}
ob.dep.notify()
}
总结
简单来说,就是 Dep 和 Watcher 在 get 钩子函数中建立关联(实际上在初始化计算属性的时候也建立了关联,但是放在以后说), 这个过程中有一个全局的 Watcher 栈做辅助,可以说这个设计相当巧妙。在 set 钩子函数和调用 vm.$set 和 vm.$delete 时运行 notify 来通知 Watcher。