# Watcher
下面看一下Watcher的实现,打开mysrc/core/observer/watcher.js
export default class Watcher {
constructor (
vm,
expOrFn,
cb,
options,
isRenderWatcher
) {
this.vm = vm
if (isRenderWatcher) {
vm._watcher = this
}
vm._watchers.push(this)
// options
if (options) {
this.deep = !!options.deep
this.user = !!options.user
this.lazy = !!options.lazy
this.sync = !!options.sync
this.before = options.before
} 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
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
}
/**
* Add a dependency to this directive.
*/
addDep (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) {
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.
isObject(value) ||
this.deep
) {
// set new value
const oldValue = this.value
this.value = value
if (this.user) {
const info = `callback for watcher "${this.expression}"`
invokeWithErrorHandling(this.cb, this.vm, [value, oldValue], this.vm, info)
} 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.
*/
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.
if (!this.vm._isBeingDestroyed) {
remove(this.vm._watchers, this)
}
let i = this.deps.length
while (i--) {
this.deps[i].removeSub(this)
}
this.active = false
}
}
}
Watcher定义了一些与dep相关的方法,单存看Watcher的逻辑比较难理解,结合之前的updateComponent方法分析逻辑就会比较清晰,在Vue的mount过程中,mountComponent函数预留的一段代码:
let updateComponent
updateComponent = () => {
vm._update(vm._render())
}
new Watcher(vm, updateComponent, noop, {
before () {
if (vm._isMounted && !vm._isDestroyed) {
// callHook(vm, 'beforeUpdate')
}
}
}, true /* isRenderWatcher */)
new Watcher传了五个参数,第一个是当前实例vm,第二个是updateComponent,第三个是一个空函数noop,第四个是一个对象,包含了一个before钩子函数,第五个参数是用来通知Watcher当前创建的是一个renderWatcher。
还记得最一开始的那个形象的比喻,watcher代表具有专项技能的工具人,那renderWatcher的意思就是代表当前是一个具备render渲染功能的watcher,所有需要执行渲染逻辑的地方都得通过renderWatcher,而data响应式就是当数据变化是执行渲染逻辑,所以每一个响应式的data数据都需要把renderWatcher收集起来,当数据发生变化是通过拦截set方法执行renderWatcher的渲染逻辑。赋予renderWatcher渲染功能,就是通过传给Watcher的第二个参数updateComponent实现的。
实际上面new Watcher时,在Watcher内部的执行逻辑是首先解析传进来的参数,expOrFn参数有两种类型情况,可以是function类型也可以是String类型,expOrFn传字符串的情况后面再分析,现在expOrFn传的就是updateComponent函数,所以this.getter = expOrFn,最后由于lazy的值为false,执行this.get方法。
在this.get方法内部执行了pushTarget(this)方法,this就是当前创建的renderWatcher实例对象。再回顾一下dep定义的pushTarget方法:
export function pushTarget (target) {
targetStack.push(target)
Dep.target = target
}
通过pushTarget方法,使得Dep.target静态属性指向当前renderWatcher实例对象,然后执行this.getter方法,上面已经知道this.getter方法就是传进来的updateComponent方法,这时候开始执行updateComponent方法
updateComponent = () => {
vm._update(vm._render())
}
updateComponent会执行_render方法,_render方法会访问data中的数据,由于这时data已经是响应式数据,所以会触发数据对象的getter。每个数据对象在执行defineReactive时都持有一个dep实例,在get方法中会执行dep.depend()方法,也就是Dep.target.addDep(this),此时Dep.target是renderWatcher实例对象,回顾在Watcher中定义的addDep方法:
addDep (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)
}
}
}
关于newDepIds的作用后面再分析,首先判断是否已经收集过当前Watcher,保证不重复收集,最后调用dep.addSub(this)方法,this就是当前wathcer实例,也就是把当前的 watcher 订阅到这个数据持有的 dep 的 subs 中。
实现收集依赖之后判断调deep如果为true,则调用traverse方法,这个方法使用递归的方式访问value的子属性,使得子属性也完成依赖收集。
然后执行popTarget方法:
export function popTarget () {
targetStack.pop()
Dep.target = targetStack[targetStack.length - 1]
}
在dep中还维护了一个targetStack栈,在vue组件嵌套的情况下,在处理父子节点过程中,先实例化父节点然后再实例化子节点,所以先创建了父组件的renderWatcher,再创建子组件的renderWatcher,而_render是从子到父节点的过程,先执行子节点的_render再执行父节点的_render。父子节点的renderWatcher依次压入targetStack栈,渲染阶段先执行子节点的_render,也就触发了子节点的依赖收集,此时收集的正是子节点的renderWatcher,子节点渲染收集完成,targetStack弹出栈顶,Dep.target等于父节点的renderWatcher,这时父节点data收集的是父节点的renderWatcher。 很巧妙的用了数据结构栈的原理实现了整个收集过程。
最后还有一个步骤执行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
}