初始化
在new Vue() 创建vue示例时,会调用_init()方法,在这个方法里面,会再调用initState方法进行初始化。初始化Computed的initComputed方法就是在这里面调用的。
initComputed:
- Object.create(null)创建一个盛放computed对象中的属性的一个对象。同时把这个对象赋值给 vm._computedWatchers。
- 遍历computed中的所有属性,获取属性的值,如果值是function就直接获取,如果不是function就获取值的get方法。所以computed是支持有两种写法:
computed:{
a () {
return 'aaa'
},
b: {
get () {
return 'bbb'
}
}
}
- 如果不是SSR,为每个属性new Watcher, 并把每个属性的值或是get方法,作为参数创建实例:
const computedWatcherOptions = { lazy: true }
if (!isSSR) {
// create internal watcher for the computed property.
watchers[key] = new Watcher(
vm,
getter || noop,
noop,
computedWatcherOptions
)
}
这里的Watcher实例和其他地方的不同,这里只是创建了一个用来收集依赖的Dep实例。
constructor (
vm: Component,
expOrFn: string | Function,
cb: Function,
options?: ?Object,
isRenderWatcher?: boolean
) {
// 省略...
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.value = this.lazy
? undefined
: this.get()
}
- 判断属性是否在data和prop中是否存在,如果不存在,就调用defineComputed方法给每个属性设置访问器属性的get和set方法。set方法就是当属性值不是function时,如果有set方法,就把它设为访问器属性的set方法,如果没有或是属性值是function,就把访问器的set方法设置为空函数。
- 如果不是SSR, get方法就是createComputedGetter函数的返回值,createComputedGetter返回一个名叫computedGetter的函数。
function createComputedGetter (key) {
return function computedGetter () {
const watcher = this._computedWatchers && this._computedWatchers[key]
if (watcher) {
if (watcher.dirty) {
watcher.evaluate()
}
if (Dep.target) {
watcher.depend()
}
return watcher.value
}
}
}
以上初始化完成,简言之就是先给每个computed属性创建一个观察者实例,当然这个观察者实例与其他的不同,这里的只是创建了一个Dep实例。没有获取值。然后给每个属性设置get和set方法。
执行
<div>{{compA}}</div>
data () {
return {
a: 1
}
},
computed: {
compA () {
return this.a + 1
}
}
以上面的代码为例:
- 模板编译过程中,会对compA取值,这是就触发了compA的get方法,这个get方法就是刚才初始化时,createComputedGetter返回的computedGetter方法。
const watcher = this._computedWatchers && this._computedWatchers[key]
if (watcher) {
if (watcher.dirty) {
watcher.evaluate()
}
if (Dep.target) {
watcher.depend()
}
return watcher.value
}
evaluate () {
this.value = this.get()
this.dirty = false
}
如果dirty是true(其实默认就是true),调用watcher.get()方法,并且把dirty设置为false,再获取该computed属性时不再进行计算,直接返回缓存的结果,缓存的位置在watcher.value。
那么watcher.get()方法又执行了什么呢?见一下代码:
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
}
export function pushTarget (_target: ?Watcher) {
if (Dep.target) targetStack.push(Dep.target)
Dep.target = _target
}
export function popTarget () {
Dep.target = targetStack.pop()
}
get方法中,首先调用pushTarget方法,如果Dep.target存在,就把它放到targetStack中,这时的Dep.target就是计算属性computed的watcher。
然后继续执行下面的逻辑,就是调用当前watcher的getter方法,这个getter方法其实是在创建watcher实例时的第二个参数,来看下
function initComputed (vm: Component, computed: Object) {
// 省略...
for (const key in computed) {
const userDef = computed[key]
const getter = typeof userDef === 'function' ? userDef : userDef.get
if (process.env.NODE_ENV !== 'production' && getter == null) {
warn(
`Getter is missing for computed property "${key}".`,
vm
)
}
if (!isSSR) {
// create internal watcher for the computed property.
watchers[key] = new Watcher(
vm,
getter || noop,
noop,
computedWatcherOptions
)
}
// 省略...
}
其实就是compA的方法。
computed: {
compA () {
return this.a + 1
}
}
当执行compA的方法时,会获取数据对象的a,我们知道,a是响应式的,当获取a的值时,会被a的访问器属性中的get方法拦截到,执行get方法,最后返回值给compA。
Object.defineProperty(obj, key, {
enumerable: true,
configurable: true,
get: function reactiveGetter () {
const value = getter ? getter.call(obj) : val
if (Dep.target) {
dep.depend()
if (childOb) {
childOb.dep.depend()
if (Array.isArray(value)) {
dependArray(value)
}
}
}
return value
},
set: function reactiveSetter (newVal) {
// 省略...
})
// dep.js
depend () {
if (Dep.target) {
Dep.target.addDep(this)
}
}
// watcher.js
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)
}
}
}
这时的Dep.target是computed属性compA的watcher,然后a的Dep调用depend方法,再调用watcher.addDep经过优化后把compA的watcher收集到a的筐里。
这样,a的筐里有了computed属性compA的watcher。继续执行就来到了finally里。
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
}
popTarget方法被调用,把watcher.get方法最开始设置的Dep.target还原回去了,也就是渲染函数了。然后调用this.cleanupDeps()清空。最后返回计算后的value值。
evaluate () {
this.value = this.get()
this.dirty = false
}
把dirty设为false,evaluate就执行完了。
function computedGetter () {
const watcher = this._computedWatchers && this._computedWatchers[key]
if (watcher) {
if (watcher.dirty) {
watcher.evaluate()
}
if (Dep.target) {
watcher.depend()
}
return watcher.value
}
然后,再执行
if (Dep.target) {
watcher.depend()
}
这时的Dep.target就又是渲染函数了,那computed属性的watcher调用depend又发生什么了呢?
depend () {
let i = this.deps.length
while (i--) {
this.deps[i].depend()
}
}
我们知道watcher的deps存放所有对watcher所依赖的响应式数据,以computed中compA的watcher为例子,它的deps里肯定存有响应式数据a的dep,这样a的dep必定会调用depend方法,那这个方法做了什么?
//dep.js
depend () {
if (Dep.target) {
Dep.target.addDep(this)
}
}
它把Dep.target放入了a的依赖收集器里了,而这时的Dep.target就是渲染函数。这样就把a与渲染函数的watcher建立了关联,当a变化时,会直接通知渲染函数的watcher进行更新了。
当修改了依赖的数据
如果修改了a的值,那么就会调用a的set方法
Object.defineProperty(obj, key, {
enumerable: true,
configurable: true,
get: function reactiveGetter () {
// 省略...
},
set: function reactiveSetter (newVal) {
const value = getter ? getter.call(obj) : val
/* eslint-disable no-self-compare */
if (newVal === value || (newVal !== newVal && value !== value)) {
return
}
/* eslint-enable no-self-compare */
if (process.env.NODE_ENV !== 'production' && customSetter) {
customSetter()
}
if (setter) {
setter.call(obj, newVal)
} else {
val = newVal
}
childOb = !shallow && observe(newVal)
dep.notify()
}
})
这里获取到值新值之后,调用dep.notify()方法。
notify () {
// stabilize the subscriber list first
const subs = this.subs.slice()
for (let i = 0, l = subs.length; i < l; i++) {
subs[i].update()
}
}
对a收集的所以观察者watcher执行update方法.
// watcher.js
update () {
/* istanbul ignore else */
if (this.lazy) {
this.dirty = true
} else if (this.sync) {
this.run()
} else {
queueWatcher(this)
}
}
a的观察者集合subs里,存有compA的watcher,也存在着渲染函数的watcher,如果是compA的watcher,先判断是不是计算属性的,如果是,就把dirty设置为true,证明计算属性compA所依赖的数据有更新,需要重新计算compA的值了。如果不是计算属性的watcher,就把这个watcher放入渲染页面的队列里,等待下次任务执行前,计算渲染页面。
在计算渲染页面时,必定是要获取compA的值的,这时又被compA的get函数拦截,
return function computedGetter () {
const watcher = this._computedWatchers && this._computedWatchers[key]
if (watcher) {
if (watcher.dirty) {
watcher.evaluate()
}
if (Dep.target) {
watcher.depend()
}
return watcher.value
}
}
这时的watcher.dirty就是true了,又在重新计算compA的值了。往下走的话,也不用担心重复收集watcher,因为addDep方法会校验有没重复收集。
总结
<div>{{compA}}</div>
data () {
return {
a: 1
}
},
computed: {
compA () {
return this.a + 1
}
}
- 为每个computed属性创建watcher实例。
- 为每个computed属性设置get,set方法。
- 模版取计算属性值会调用计算属性的get方法,获取值并把dirty设为false,对数据缓存。同时在计算值的时候,需要获取data属性时,又触发data的get方法,这时,data再把computed的watcher收集起来。然后再收集起渲染函数的watcher。
- 当data变化时,会notify计算属性的watcher和渲染函数的watcher,计算属性的watcher调用update方法,只是把dirty设置为true,就是说要重新计算了。渲染函数的watcher的update方法是把渲染函数的更新放入到队列里,等待下次事件循环的时候执行。 5.在渲染函数执行过程中,肯定会获取compA的值,这样又触发了compA的get方法,就又调用重新计算compA的方法并返回新的值了。
终于缕顺了,真不容易,赶紧记下来。😄