Watcher 分为三种类型,Computed Watcher、用户 Watcher(侦听器)、渲染 Watcher
首先是首次渲染的时候创建 Watcher 的时候。
// 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 */)
在 mountComponent 这个方法中创建了 Watcher 示例,传入了几个参数。
下面分析 Watcher 类中执行的主要步骤
// parse expression for getter
if (typeof expOrFn === 'function') {
this.getter = expOrFn
} else {
// expOrFn 是字符串的时候,例如 { 'person.name': function... }
// parsePath('person.name') 返回一个函数获取 person.name 的值
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()
这里通过判断 expOrFn 是 function 还是 字符串来区分渲染 Watcher 还是 用户 Watcher ,然后进行分类讨论。
然后调用 this.get() 方法,在 get 中会将当前的 Dep.target 进行入栈以及渲染等操作。入栈是为了处理父子组件嵌套的情况。
/**
* Evaluate the getter, and re-collect dependencies.
*/
get () {
// 设置 Dep.target 属性
pushTarget(this)
let value
const vm = this.vm
try {
// 将模板转换成虚拟 DOM ,然后将虚拟 DOM 转换成真实 DOM,过程中会触发属性的 get,在 get 中去收集依赖
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
}
在 get 中会通过调用 this.getter.call(vm, vm) 来更新视图。如果是 Computed 或者 用户 Watcher 还会将最终得到的 value 返回。
在数据更新的时候会调用 dep 的 notify 方法发送通知,遍历 dep 的 subs 数组,调用所有的 Watcher 的 update 方法更新视图。
// 发布通知
notify () {
// stabilize the subscriber list first
// 使用 slice 对数组进行克隆,为后面数组根据 id 排序做准备
const subs = this.subs.slice()
if (process.env.NODE_ENV !== 'production' && !config.async) {
// subs aren't sorted in scheduler if not running async
// we need to sort them now to make sure they fire in correct
// order
subs.sort((a, b) => a.id - b.id)
}
// 调用每个订阅者的 update 方法实现更新
for (let i = 0, l = subs.length; i < l; i++) {
subs[i].update()
}
}
在 notify 中遍历 subs 调用所有的 Watcher 的 update 方法。下面是 update 方法的实现。
/**
* Subscriber interface.
* Will be called when a dependency changes.
*/
update () {
/* istanbul ignore else */
if (this.lazy) {
// lazy 为 true 说明是 Computed Watcher
this.dirty = true
} else if (this.sync) {
this.run()
} else {
// 是渲染 Watcher
queueWatcher(this)
}
}
在 update 中如果是渲染 Watcher 会调用 queueWatcher 将当前的 Watcher 添加到 Watcher 任务队列中。
/**
* Push a watcher into the watcher queue.
* Jobs with duplicate IDs will be skipped unless it's
* pushed when the queue is being flushed.
*/
export function queueWatcher (watcher: Watcher) {
const id = watcher.id
// has 是一个对象,用于存储所有已经处理过的 Watcher,防止 Watcher 被重复处理
if (has[id] == null) {
// 如果为 null 说明没有被处理过
has[id] = true
// flushing 为 true 说明当前的队列正在被处理,即队列中存储的 Watcher 对象正在被处理
if (!flushing) {
// 如果没有在处理,就直接将当前 Watcher 放到队尾
queue.push(watcher)
} else {
// if already flushing, splice the watcher based on its id
// if already past its id, it will be run next immediately.
// 如果正在被处理,需要找到一个合适的位置将当前的 Watcher 插入到合适的位置
let i = queue.length - 1
while (i > index && queue[i].id > watcher.id) {
i--
}
queue.splice(i + 1, 0, watcher)
}
// queue the flush
if (!waiting) {
waiting = true
if (process.env.NODE_ENV !== 'production' && !config.async) {
// 遍历队列,调用 update 方法
flushSchedulerQueue()
return
}
// 如果是生产环境,将处理函数交给 nextTick
nextTick(flushSchedulerQueue)
}
}
}
执行 queueWatcher 的最后会去调用 flushSchedulerQueue 方法遍历队列执行更新。
/**
* Flush both queues and run the watchers.
*/
function flushSchedulerQueue () {
currentFlushTimestamp = getNow()
// 更改标记,标识正在处理队列
flushing = true
let watcher, id
// Sort queue before flush.
// This ensures that:
// 1. Components are updated from parent to child. (because parent is always
// created before the child)
// 2. A component's user watchers are run before its render watcher (because
// user watchers are created before the render watcher)
// 3. If a component is destroyed during a parent component's watcher run,
// its watchers can be skipped.
// 根据 id 进行排序,即依据 Watcher 的创建顺序进行排序。
queue.sort((a, b) => a.id - b.id)
// do not cache length because more watchers might be pushed
// as we run existing watchers
for (index = 0; index < queue.length; index++) {
watcher = queue[index]
if (watcher.before) {
watcher.before()
}
id = watcher.id
has[id] = null
watcher.run()
// in dev build, check and stop circular updates.
if (process.env.NODE_ENV !== 'production' && has[id] != null) {
circular[id] = (circular[id] || 0) + 1
if (circular[id] > MAX_UPDATE_COUNT) {
warn(
'You may have an infinite update loop ' + (
watcher.user
? `in watcher with expression "${watcher.expression}"`
: `in a component render function.`
),
watcher.vm
)
break
}
}
}
// keep copies of post queues before resetting state
const activatedQueue = activatedChildren.slice()
const updatedQueue = queue.slice()
resetSchedulerState()
// call component updated and activated hooks
callActivatedHooks(activatedQueue)
callUpdatedHooks(updatedQueue)
// devtool hook
/* istanbul ignore if */
if (devtools && config.devtools) {
devtools.emit('flush')
}
}
在 flushSchedulerQueue 中遍历队列执行了的 run 方法
/**
* 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) {
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)
}
}
}
}