Appearance
四、Watcher类
首次渲染的时候数据的执行过程,数据改变的时候数据的执行过程.
三种类型
- Computed Watcher
- 用户 Watcher (侦听器)
- 渲染 Watcher
渲染Watcher是在instance/lifecycle.js的mountComponent中创建的,创建了Watcher对象
js
// 创建了一个Watcher对象,并把updateComponent传入,所以其执行是在Watcher中调用的
// 第三个参数是noop空函数,第三个参数对用户和计算属性watcher是有用的,渲染watcher没有用
// 第四个参数是对象,里面定义了before函数,触发生命周期钩子函数
// 最后一个参数是true,标记这个watcher是渲染watcher
new Watcher(vm, updateComponent, noop, {
before () {
if (vm._isMounted && !vm._isDestroyed) {
callHook(vm, 'beforeUpdate')
}
}
}, true /* isRenderWatcher */)
进入watcher构造函数中了解其执行过程
js
export default class Watcher {
...
constructor (
vm: Component,
expOrFn: string | Function,
cb: Function,
options?: ?Object,
// 是否是渲染Watcher,有三种Watcher
/**
* watcher有三种
* - 第一种是渲染Watcher,当前的Watcher
* - 计算属性的Watcher
* - 侦听器的Watcher
*/
isRenderWatcher?: boolean
) {
this.vm = vm
// 判断是不是渲染watcher,如果是就把当前watcher记录到实例的_watcher中
if (isRenderWatcher) {
vm._watcher = this
}
// 记录到_watchers中,这里面的watcher不仅仅是渲染watcher,还有计算属性watcher和侦听器的watcher
vm._watchers.push(this)
// options
// 这些选项都与渲染watcher无关,默认这些值都是false,非渲染的watcher会传入一些选项
if (options) {
this.deep = !!options.deep
this.user = !!options.user
// lazy 延迟执行,watcher要更新视图,那lazy就是是否延迟更新视图,当前是首次渲染要立即更新所以值是false,如果是计算属性的话是true,当数据发生变化之后才去更新视图
this.lazy = !!options.lazy
this.sync = !!options.sync
// 传入的before函数,会触发生命周期的beforeUpdate
this.before = options.before
} else {
this.deep = this.user = this.lazy = this.sync = false
}
// cb是构造函数的第三个参数,渲染参数是noop空函数,当用watcher的时候会传入一个回调,会对比新旧两个值
this.cb = cb
// watcher唯一标识
this.id = ++uid // uid for batching
// 标识当前watcher是否是活动的
this.active = true
this.dirty = this.lazy // for lazy watchers
// 记录与watcher相关的dep对象
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
// 第二个参数如果是function就直接把变量赋值给getter
if (typeof expOrFn === 'function') {
this.getter = expOrFn
} else {
// 如果是字符串的话要进一步处理,如何处理先不关注,如果是侦听器的话第二个参数传入的就是字符串
// 例如:watch:{ 'person': function ...}
// parsePath这个函数的作用是生成一个函数来获取属性的值,将这个函数返回的新函数记录到getter中
// 此时的getter是一个函数,这个函数的作用是返回属性结果,获取属性 person 的值,触发了这个属性的getter,触发 getter 的时候会去收集依赖
// 此时并没有执行而是记录下来了
this.getter = parsePath(expOrFn)
// 做了一些错误的处理,开发环境getter不存在就会有警告
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,如果当前不要求延迟执行就立即执行get方法
// this.lazy如果是计算属性的watcher是true,延迟执行,其他watcher是false立即执行
this.value = this.lazy
? undefined
: this.get()
}
/**
* Evaluate the getter, and re-collect dependencies.
*/
get () {
// 调用pushTarget,将当前的Watcher对象放入栈中
// 每个组件对应一个Watcher,Watcher会去渲染视图,如果组件有嵌套的话会先渲染内部的组件,所以要将父组件的Watcher先保存起来,这是这个pushTarget的作用
pushTarget(this)
let value
const vm = this.vm
try {
// 最关键的一句话
// 这句话调用了getter,getter存储的是传入的第二个参数,且是函数,首次渲染是updateComponent,所以在get方法的内部调用了updateComponent,并且改变了函数内部的this指向到Vue实例vm,并且传入了vm
// 这里将虚拟DOM转化成了真实DOM并更新到页面中
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)
}
// 将watcher从栈中弹出
popTarget()
// 会把当前watcher会从dep.subs数组中移除,把watcher里面的dep也移除
this.cleanupDeps()
}
return value
}
...
}
下面看当数据更新的时候watcher是如何工作的?
notify
当数据更新的时候,会调用dep.notify方法,进入observer/dep.js
js
// 发布通知
notify () {
// stabilize the subscriber list first
// subs数组是watcher对象数组,这里要进行克隆,下面要进行排序,按照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
// 按照watcher的创建顺序进行排序,保证执行watcher的顺序是正确的
subs.sort((a, b) => a.id - b.id)
}
// 循环subs数组,
for (let i = 0, l = subs.length; i < l; i++) {
// 会调用watcher中的update方法
subs[i].update()
}
}
}
进入update方法,返回到了watcher类
update
js
update () {
/* istanbul ignore else */
// 渲染watcher中,lazy和sync是false,会执行queueWatcher
if (this.lazy) {
this.dirty = true
} else if (this.sync) {
this.run()
} else {
queueWatcher(this)
}
}
渲染watcher要执行的是queueWatcher
queueWatcher
js
// 接收一个参数,watcher对象
export function queueWatcher (watcher: Watcher) {
// 获取watcher对象的id
const id = watcher.id
// 判断has[id],has是个对象,如果是null说明watcher没有被处理,防止重复处理
if (has[id] == null) {
// 标识已经处理过了
has[id] = true
// 这段代码的功能是将watcher对象添加到队列中
// flushing是正在刷新的意思,如果其为true说明队列正在被处理,队列queue就是watcher对象,是watcher对象正在被处理
// 判断队列没有被处理的时候,将watcher直接放到队列的末尾中
if (!flushing) {
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.
// 获取队列的长度
let i = queue.length - 1
// index是处理到了队列的第几个元素,i > index 表示该队列还没有被处理完,就获取队列中的watcher对象,判断id是否大于当前正在处理的id,如果大于就将i--
while (i > index && queue[i].id > watcher.id) {
i--
}
// 这样就把处理的watcher放到了合适的位置中
queue.splice(i + 1, 0, watcher)
}
// queue the flush
// 判断当前队列是否被执行,如果没有被执行就进入
if (!waiting) {
waiting = true
// 如果是开发环境的话,就直接执行flushSchedulerQueue函数,如果是生产环境,将flushSchedulerQueue传给nextTick
if (process.env.NODE_ENV !== 'production' && !config.async) {
// 这个函数会遍历所有的watcher,并调用watcher的run方法
flushSchedulerQueue()
return
}
nextTick(flushSchedulerQueue)
}
}
}
下面看一下flushSchedulerQueue是怎么执行的
flushSchedulerQueue
js
// 标记true,表示正在处理队列
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)
// 组建的用户watcher要在其对应的渲染watcher之前运行,因为用户watcher是在渲染watcher之前创建的
// 3. If a component is destroyed during a parent component's watcher run,
// its watchers can be skipped.
// 如果一个组件在父组件执行之前被销毁了,那应该跳过
// 先对queue进行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
// 遍历queue中的所有watcher,不要缓存lenth,因为watcher在执行的过程中在队列中可能会加入新的watcher
for (index = 0; index < queue.length; index++) {
// 获取watcher,判断其是否有before函数,有before函数是在渲染watcher中才有的,触发钩子函数beforeUpdate
watcher = queue[index]
if (watcher.before) {
watcher.before()
}
// 获取watcher的id,将其处理为null,下次调用的时候还能正常被运行
id = watcher.id
has[id] = null
// 调用watcher的run方法
watcher.run()
...
}
点击watcher的run方法,进入
run
js
run () {
// 标记当前watcher是否是存活的状态,默认为true,可处理
if (this.active) {
// 调用其get方法,如果是渲染watcher会调用getter,执行updateComponent方法渲染DOM更新页面
// 之后用value记录返回结果,如果是渲染watcher没有返回结果,value是undefined,渲染函数的cb是noop空函数.
// 如果是用户watcher,继续执行,获取旧值记录新值,调用cb回调函数,侦听器的function就是回调函数,
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) {
// 如果是用户watcher,添加异常处理
try {
this.cb.call(this.vm, value, oldValue)
} catch (e) {
handleError(e, this.vm, `callback for watcher "${this.expression}"`)
}
} else {
// 如果是其他watcher,直接调用
this.cb.call(this.vm, value, oldValue)
}
}
}
}
总结
- 当数据变化之后调用dep.notify方法去通知watcher
- 先把watcher放到一个队列中,遍历队列,调用队列中元素的run方法
- run方法中最终调用了updateComponent函数