https://github.com/KingComedy/vue-debugger
// 主要属性初始化
new Watcher(vm, expOrFn, cb, options)
/*
id 是自增属性,在执行更新时会对watcher根据id进行排序,
因为数据处理watcher的创建顺序是 computedWatcher => userWatcher => renderWatcher
所以组件内watcher的执行顺序也是 computedWatcher => userWatcher => renderWatcher
这样能保证renderWatcher执行dom更新时,computed属性值是最新的
*/
watcher.id
/*
expOrFn参数:key或者函数
computedWatcher: 传的是函数, 用于获取computed属性的值。如果传的是对象,则是对象的 get
userWatcher: 传的是属性名, 通过属性名获取所对应的函数
renderWatcher: 传的是updateComponent即组件更新函数
最后都会转为函数,保存在watcher.getter
*/
watcher.expOrFn
watcher.getter
/*
value: 当前watcher的值,为执行getter函数 的结果值,
computedWatcher: getter函数执行的值
userWatcher:所对应属性的值
renderWatcher: undefined
*/
watcher.value
/*
cb参数:
computedWatcher和renderWatcher: 传的是空函数
userWatcher: 传的是回调函数
*/
watcher.cb
/*
lazy是在options参数里
只有computedWatcher的lazy和dirty属性是true
lazy为true,watcher实例化时,不执行get函数
dirty是对观察的属性做标记,可以理解为观察的该属性是否是脏数据,即做过改变,如果为true,则需要重新获取属性的值,如果为false,则读取缓存的值
*/
watcher.lazy
watcher.dirty
/*
只有userWatcher的user属性是true
表示是用户手动传入的回调函数,因此在执行cb回调函数时,要try、catch捕获异常
*/
watcher.user
总结:
- 1、组件内部watcher的创建顺序和执行顺序都为 computedWatcher => userWatcher => renderWatcher
- 2、computedWatcher的lazy和dirty属性都为true,lazy用于标识是否在创建watcher时执行watcher.get函数,dirty用于标识数据是否需要重新获取,true为重新获取,false则读取缓存
- 3、userWatcher的user属性为true,执行cb回调函数时会用try/catch执行捕获异常
// 主要代码
/*
get函数触发时机:除了computedWatcher外,userWatcher和renderWatcher实例化时都会执行一次get函数
get函数执行流程:
1、将Dep类的静态属性target设置为当前watcher,并推入存储watcher的栈中
2、然后执行getter函数。
computedWatcher:执行getter函数,其实就是执行computed属性的对应的函数或者get,如果执行的函数里,有依赖到其他属性,这时就会建立其他属性和当前computedWatcher的依赖关系
userWatcher:执行getter方法,其实就是获取当前属性的值,并设置当前userWatcher.value
renderWatcher: 执行updateComponent函数,即执行render和patch,在render阶段时,如果读取到组件里的属性,依旧会触发属性的get,即同时与renderWatcher建立依赖关系
3、将Dep类的静态属性target设置为当前watcher,并退出存储watcher的栈
*/
get () {
// 设置Dep.target = this
pushTarget(this)
let value
const vm = this.vm
try {
value = this.getter.call(vm, vm)
} catch (e) {
// 如果是监听器 或者调用$watch,需要捕获异常
if (this.user) {
handleError(e, vm, `getter for watcher "${this.expression}"`)
} else {
throw e
}
} finally {
popTarget()
}
return value
}
/*
update函数触发时机:watcher所观察的属性 触发更新
update函数执行流程:
1、如果this.lazy为true,即当前watcher属于computedWatcher,只是设置dirty属性
2、如果this.sync, 执行run函数
3、否则将当前watcher入队,后面在异步更新时,会遍历执行watcher的run方法
*/
update () {
/* istanbul ignore else */
// computed
if (this.lazy) {
this.dirty = true
} else if (this.sync) {
// 同步执行
this.run()
} else {
// 进入异步刷新队列
queueWatcher(this)
}
}
/*
run函数触发时机是 vue执行异步更新时,会遍历触发watcher的run函数
run函数执行流程:
1、执行get函数
2、如果是userWatcher,要执行cb 回调函数
*/
run () {
const value = this.get()
if (value !== this.value || isObject(value) || this.deep) {
// set new value
const oldValue = this.value
this.value = value
// this.user 表示是用户手动调用的watcher,如组件的watch, 需要加 捕获异常
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)
}
}
}
总结:
- watcher.get函数作用一是获取对应属性的值,二是与观察的属性建立关系的过程
- 不同watcher执行的get:computedWatcher执行get会执行属性所对应的函数,同时与依赖属性的dep建立关系,并返回值。userWatcher即会通过属性名,获取到组件的属性的值。renderWatcher则是执行updateComponent函数,首次执行是初始化和挂载组件,后面则是执行组件更新
- 当属性改变时,执行的是watcher的update。除了computedWatcher外,userWatcher和renderWatcher都会进入异步刷新队列,即执行queueWatcher(this)
- userWatcher和renderWatcher执行更新,最终会执行watcher.run函数
- watcher.run函数主要执行 get函数 和 cb回调函数。因为renderWatcher的cb传的是空函数,所以renderWatcher的run主要还是执行get函数,即更新函数updateComponent。userWatcher则需要先执行get函数获取到新的值,并传入cb回调函数
- 如果computed属性存在,执行initComputed(在src\core\instance\state.js里定义)
- 创建watcher为一个空对象,用于存储每个computed属性的watcher,存于组件的_computedWatchers属性
- 遍历computed的每个属性
- 为每个属性创建一个对应的watcher,并且lazy设置为true,即不立即执行属性的get, 并且watcher.dirty 也为true。dirty相当于标记当前属性脏数据,即是否已发生变化,是的话要重新读取,不是的话读取缓存
- 检验每个属性 是否与 props 或者 data里的属性重复
- 如果没有重复,对computed里的每个属性做响应式处理,如果属性是一个函数,则属性的set为空函数(所以手动设置computed属性的值是无效的),属性的get为createComputedGetter(key)返回的函数computedGetter(在src\core\instance\state.js定义)。如果属性是对象,即手动设置的set,get,则属性set可以生效,get则会判断对象的cache的属性,如果没有关闭缓存,则还是createComputedGetter(key)返回的函数computedGetter
- 在组件render阶段时,读取到computed里的属性时,执行computedGetter
- 通过属性名,在组件的_computedWatchers属性,获取当前属性对应的watcher
- 如果watcher.dirty属性为true,调用watcher.evaluate(),因为首次读取属性时,dirty是true,所以首次获取值时会调用watcher.evaluate
- evaluate会触发当前属性的get,即会执行传入的回调函数,并将dirty设置为false。
- 先执行computed的回调函数,如果回调函数里有依赖了其他属性如this.a和this.b,则又会触发a和b属性的get,则会将当前computed属性的computedWatcher收集到a,b属性的依赖收集dep里,当a,b属性发生更新时,它们的dep会执行computedWatcher的update,将computedWatcher的dirty设置为true,表示数据已改变,需要重新获取,而不是读取缓存。
- a,b发生更新,页面重新render时,又需要读取computed的属性值时,即再次执行computedGetter,此时dirty为true,则触发watcher的get,即重新执行下回调函数,返回最新的值,如果dirty为false,则返回旧的watcher的值
computed: {
keyValue () {
return this.count + this.key
}
},
- 当computed初始化时,会为keyValue创建一个computedWatcher,并将函数作为computedWatcher的回调函数,并设置computedWatcher的lazy和dirty为true,并为keyValue重写set、get方法。set为空函数,get则会执行computedGetter
- 当render阶段时,读取到keyValue值时,触发属性的get,即执行computedGetter
- 因为此时dirty为true,所以会执行computedWatcher.evaluate()函数
- evaluate中会执行computedWatcher.get(),即执行computedWatcher的回调函数,在执行keyValue函数时,又会触发count和key属性的get,会将当前computedWatcher实例收集到 count和key属性的依赖收集器dep里
- 执行完computedWatcher.get后,会将computedWatcher.dirty设置为false,如果下次组件重新render时,dirty还是false,则读取缓存的值,即原来computedWatcher.value
- 在keyValue的computedWatcher 与 count和key属性的dep建立关系后,如果count或者key发生更新,会执行computedWatcher.update,主要是将dirty设置会true,然后再读取keyValue时,就又会执行computedWatcher.evaluate()函数了
- initWatch(vm, opts.watch)(在src\core\instance\state.js文件下)
- 遍历watch的所有属性,根据key取出属性对应的值handler
- 如果handler是数组,则遍历数组,执行createWatcher(如下面这种用法,vue2文档并没有, 一般也没这么使用)
watch: { 'count': [(n) => { console.log(n) }, () => { console.log(n) }] }
- handler如果不是,则直接执行createWatcher(vm, key, handler)
- createWatcher函数会对handler进行处理为函数,因为handler可能为函数、对象、字符串。
- createWatcher主要源码:
function createWatcher ( vm: Component, expOrFn: string | Function, handler: any, options?: Object ) { // watch有三种形式:对象、方法、字符串 /* 如: watch: { count: { handler: (newVal, oldVal) {}, deep: true, immediate: true }, 'key'(n) {}, 'key': 'method' } */ if (isPlainObject(handler)) { options = handler handler = handler.handler } if (typeof handler === 'string') { handler = vm[handler] } // 最终会调用vm.$watch(expOrFn, handler, options)。expOrFn为key,handler为已经被处理的函数,deep和immediate属性会存在options中 return vm.$watch(expOrFn, handler, options) }
主要源码:
Vue.prototype.$watch = function (
expOrFn: string | Function,
cb: any,
options?: Object
): Function {
const vm: Component = this
// 如果cb参数是对象,还是调用createWatcher
if (isPlainObject(cb)) {
return createWatcher(vm, expOrFn, cb, options)
}
options = options || {}
// 标记为userWatcher
options.user = true
const watcher = new Watcher(vm, expOrFn, cb, options)
// 如果immediate为true,立即执行一次回调函数
if (options.immediate) {
try {
cb.call(vm, watcher.value)
} catch (error) {
handleError(error, vm, `callback for immediate watcher "${watcher.expression}"`)
}
}
return function unwatchFn () {
watcher.teardown()
}
}
如何区分不同的watcher:
- lazy 为true 则为computedWatcher
- user 为true 则为userWatcher
- getter 为updateComponent 则为renderWatcher
初始化过程
- computedWatcher和userWatcher是每个属性都会创建对应的watcher,而renderWatcher一个组件只会创建一个
- computedWatcher实例化时不会执行get函数,即还没有收集依赖。userWatcher实例化时,会执行get函数收集依赖,如果immediate为true会立即执行cb回调函数。renderWatcher实例化时,即执行updateComponent,开始组件的render和挂载
- 执行watcher.get函数
- pushTarget(watcher):将Dep.target设置为当前watcher
- 执行watcher.getter,watcher.getter执行期间,只要有读取到属性(已经做过响应式处理的属性),就会建立属性与watcher依赖收集
- 依赖收集过程:
- 属性的get函数 会执行dep.depend()
- dep.depend() 会将执行当前Dep.target即watcher的addDep(dep)函数,并传入当前dep
- watcher.addDep:会将dep存入newDeps实例属性上,并调用dep.addSub(watcher),并传入watcher
- dep.addSub:会将传入的watcher存入当前dep的subs实例属性上,然后dep和watcher就互相建立好联系
- watcher.getter执行结束后,会调用popTarget,将Dep.target设置为空
- 属性更新时,会遍历执行dep中watcher的update函数
- computedWatcher:computedWatcher执行update会将dirty设置为true,表示需要重新获取属性的值。当其他地方需要读取当前属性的值时,就会重新执行watcher.get,获取最新的值
- userWatcher:执行update会将当前watcher存入异步更新队列,最后在刷新队列的时候会执行watcher.run函数,即先执行get函数 获取属性最新的值,然后将新旧值传入cb,执行cb回调函数
- renderWatcher:执行update会将当前watcher存入异步更新队列,在刷新队列前会将watcher队列进行排序,renderWatcher会在最后执行,所以是所有的computedWatcher和userWatcher执行完后,才会执行renderWatcher,即主要是执行get,updateComponent函数,执行组件的更新
renderWatcher执行组件的挂载和更新的过程,可以参考:https://blog.csdn.net/comedyking/article/details/115670343
watcher队列执行异步更新的过程。可以参考:https://blog.csdn.net/comedyking/article/details/117702133