二、依赖收集
在调用defineReactive函数的时候,会通过Object.defineProperty绑定getter和setter,绑定getter,触发getter的过程就是依赖收集。首先会调用 const value = getter ? getter.call(obj) : val来计算出他的value,在getter的最后会把value返回。依赖收集的过程是从判断Dep.target开始
// src/core/observer/index.js
/**
* Define a reactive property on an Object.
*/
export function defineReactive (
obj: Object,
key: string,
val: any,
customSetter?: ?Function,
shallow?: boolean
) {
...
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) {
...
}
})
}
Dep是一个类,他的目的是把数据和watcher之间联系起来,是一个中间的纽带,Dep.target在flow中的定义是一个Watcher,如果有watcher的话,会执行dep.depend(),相当于执行了Dep.target.addDep(this)也就是watcher.addDep(this)
// src/core/observer/dep.js
/**
* 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 = []
}
...
depend () {
if (Dep.target) {
Dep.target.addDep(this)
}
}
}
那么渲染watcher会在什么时候执行呢(什么时机调用Watcher类)。在vue初始化的过程中,会先执行mountcompoennt方法,调用updateComponent = ()=> vm._update(vm._render(), hydrating),updateComponent实际上会作为new Watcher的第二个参数传入
// src/core/instance/lifecycle.js
export function mountComponent (
vm: Component,
el: ?Element,
hydrating?: boolean
): Component {
...
let updateComponent
...
updateComponent = () => {
vm._update(vm._render(), hydrating)
}
}
// we set this to vm._watcher inside the watcher's constructor
// since the watcher's initial patch may call $forceUpdate (e.g. inside child
// component's mounted hook), which relies on vm._watcher being already defined
new Watcher(vm, updateComponent, noop, {
before () {
if (vm._isMounted && !vm._isDestroyed) {
callHook(vm, 'beforeUpdate')
}
}
}, true /* isRenderWatcher */)
...
}
当执行new Watcher的时候,constructor会执行vm._watcher=this,然后会让当前的getter = expOrFn,expOrFn就是在创建渲染watcher传入的vm._update(vm._render(), hydrating),最后调用this.get()。执行get方法,首先会调用pushTarget方法,pushTarget会执行Dep.target = target,也就是让Dep的target等于了当前的渲染watcher。还会往targetStack数组中,把当前的wather实例push进去,与此对应的还有popTarget方法,他给当前targetStack做一个pop操作,删除最后一项,同时让Dep.target = targetStack[targetStack.length - 1],这样做的目的是当出现了组件嵌套的情况,可以把子的组件pop,还原父的组件watcher。也就是说执行pushTarget会把当前的渲染wathcer作为Dep.target。执行完之后会调用this.getter,也就是vm._update(vm._render(), hydrating)
// src/core/observer/watcher.js
export default class Watcher {
...
constructor (
vm: Component,
expOrFn: string | Function,
cb: Function,
options?: ?Object,
isRenderWatcher?: boolean
) {
this.vm = vm
if (isRenderWatcher) {
vm._watcher = this
}
...
this.deps = []
this.newDeps = []
this.depIds = new Set()
this.newDepIds = new Set()
...
// parse expression for getter
if (typeof expOrFn === 'function') {
this.getter = expOrFn
} else {
this.getter = parsePath(expOrFn)
if (!this.getter) {
this.getter = noop
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.
*/
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
}
...
}
在执行vm._render()的过程中,会执行 vnode = render.call(vm._renderProxy, vm.$createElement)也就是render函数,会访问到模板当中定义的数据,会触发响应式的getter。触发getter,会执行到dep.depend(),也就是 Dep.target.addDep(this),对应到watcher当中就是dep.addSub(this)在dep中,是this.subs.push(sub),那么subs就作为了一个订阅者
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的get执行到最后会调用popTarget()来恢复之前的渲染watcher,然后调用 this.cleanupDeps()来清除之前的dep,addDep方法会往newDepIds和newDeps中添加新的数据,cleanupDeps则会执行this.depIds = this.newDepIds 和 this.deps = this.newDeps然后清空newDeps和newDepIds,然后当下次一进入的时候,会执行dep.removeSub(this)清除之前的数据。可以看到addDep函数中已经做过一层逻辑上的优化,if (!this.newDepIds.has(id))也就是说如果已经有了,那么他不会重复添加,那么为什么还需要执行cleanupDeps是因为当页面的一些数据在上一次数据更新的时候是被依赖的,但是到了新的一次数据更新,他已经不在页面的渲染依赖当中了,也就是说给他不再需要去触发updatecomonent函数了。所以在每次执行完之后,他需要执行cleanupDeps来清除之前的依赖
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
}