Watcher接收的参数当中expOrFn定义了用以获取watcher的getter函数。expOrFn可以有2种类型:string或function.若为string类型,首先会通过parsePath方法去对string进行分割(仅支持.号形式的对象访问)。在除了computed选项外,其他几种实例化watcher的方式都是在实例化过程中完成求值及依赖的收集工作:this.value = this.lazy ? undefined : this.get().在Watcher的get方法中:
!!!前方高能
get () { // pushTarget即设置当前的需要被执行的watcher pushTarget(this) let value const vm = this.vm if (this.user) { try { // $watch(function () {}) // 调用this.getter的时候,触发了属性的getter函数 // 在getter中进行了依赖的管理 value = this.getter.call(vm, vm) console.log(value) } catch (e) { handleError(e, vm, `getter for watcher "${this.expression}"`) } } else { // 如果是新建模板函数,则会动态计算模板与data中绑定的变量,这个时候就调用了getter函数,那么就完成了dep的收集 // 调用getter函数,则同时会调用函数内部的getter的函数,进行dep收集工作 value = this.getter.call(vm, vm) } // "touch" every property so they are all tracked as // dependencies for deep watching // 让每个属性都被作为dependencies而tracked, 这样是为了deep watching if (this.deep) { traverse(value) } popTarget() this.cleanupDeps() return value }
一进入get方法,首先进行pushTarget(this)的操作,此时Vue当中Dep.target = 当前这个watcher,接下来进行value = this.getter.call(vm, vm)操作,在这个操作中就完成了依赖的收集工作。还是拿文章一开始的demo来说,在vue实例化的时候传入了watch选项:
props: { a: { type: Object, default () { return { key1: 'a', key2: { a: 'b' } } } } }, watch: { a (newVal, oldVal) { console.log(newVal, oldVal) } },
在Vue的initState()开始执行后,首先会初始化props的属性为getter/setter函数,然后在进行initWatch初始化的时候,这个时候初始化watcher实例,并调用get()方法,设置Dep.target = 当前这个watcher实例,进而到value = this.getter.call(vm, vm)的操作。在调用this.getter.call(vm, vm)的方法中,便会访问props选项中的a属性即其getter函数。在a属性的getter函数执行过程中,因为Dep.target已经存在,那么就进入了依赖收集的过程:
if (Dep.target) { // Dep.target.addDep(this) // 即添加watch函数 // dep.depend()及调用了dep.addSub()只不过中间需要判断是否这个id的dep已经被包含在内了 dep.depend() // childOb也添加依赖 if (childOb) { childOb.dep.depend() } if (Array.isArray(value)) { dependArray(value) } }
dep是一开始初始化的过程中,这个属性上的dep属性。调用dep.depend()函数:
depend () { if (Dep.target) { // Dep.target为一个watcher Dep.target.addDep(this) } }
Dep.target也就刚才的那个watcher实例,这里也就相当于调用了watcher实例的addDep方法: watcher.addDep(this),并将dep观察者传入。在addDep方法中完成依赖收集:
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) } } }
这个时候依赖完成了收集,当你去修改a属性的值时,会调用a属性的setter函数,里面会执行dep.notify(),它会遍历所有的订阅者,然后调用订阅者上的update函数。
initData过程和initProps类似,具体可参见源码。
initComputed
以上就是在initProps过程中Vue是如何进行依赖收集的,initData的过程和initProps类似,下来再来看看initComputed的过程.
在computed属性初始化的过程当中,会为每个属性实例化一个watcher:
const computedWatcherOptions = { lazy: true } function initComputed (vm: Component, computed: Object) { // 新建_computedWatchers属性 const watchers = vm._computedWatchers = Object.create(null) for (const key in computed) { const userDef = computed[key] // 如果computed为funtion,即取这个function为getter函数 // 如果computed为非function.则可以单独为这个属性定义getter/setter属性 let getter = typeof userDef === 'function' ? userDef : userDef.get // create internal watcher for the computed property. // lazy属性为true // 注意这个地方传入的getter参数 // 实例化的过程当中不去完成依赖的收集工作 watchers[key] = new Watcher(vm, getter, noop, computedWatcherOptions) // component-defined computed properties are already defined on the // component prototype. We only need to define computed properties defined // at instantiation here. if (!(key in vm)) { defineComputed(vm, key, userDef) } } }