computed属性的意义:如果组件渲染生成虚拟节点的过程中,需要通过一个方法计算得到某个返回值,可以在渲染的时候直接调用这个方法,但是如果这次的渲染不是由方法所依赖的变量值的变化导致的,那么再计算一次就没有必要。
computed的每个属性(假设为c)会被挂载在vm上,为c新建一个对应的watcher,重写c的get方法如下
function computedGetter () { var watcher = this._computedWatchers && this._computedWatchers[key]; if (watcher) { if (watcher.dirty) { watcher.evaluate(); } if (Dep.target) { watcher.depend(); } return watcher.value } }
这里的watcher在new的时候,其dirty=lazy=true,所以组件在创建周期渲染生成虚拟节点的时候,要获取c的值,到这里发现dirty为true,调用watcher的evaluate方法
Watcher.prototype.evaluate = function evaluate () { this.value = this.get(); this.dirty = false; } ;
watcher的get方法调用的时候,先把自己入栈,用vm调用watcher的getter方法,这里的getter方法就是c指向的那个方法,vm调用getter方法的时候,计算c的值,就会调用所有c所依赖的属性(简称d)的get方法,
那么就会收集watcher(因为之前已经defineReacive(data)了)。同时在evaluate方法中,会把dirty设置为false,因为此时value的数据是全新的可用的。
注意在computedGetter方法中,evaluate方法后面,还有一句if (Dep.target) { watcher.depend(); },因为此时c对应的watcher在evaluate方法完成后会出栈,之前栈顶的watcher会暴露,注意此时处于组件的创建周期中,这次的渲染是在vm.$mount--->
new renderWatcher中,因为renderWatcher的lazy为false,new的最后会调用watcher.get()方法,renderWatcher入栈,调用watcher.getter方法(也就是vm._update(vm._render(), hydrating);)这个时候调用的render方法,所以computedWatcher出栈后
栈顶的是renderWatcher,watcher.depend()这一句就是将收集computedWatcher的那些属性的get方法再收集一次renderWatcher。这种情形也就是为什么Dep和watcher要设计成你中有我我中有你的原因之一。
注意此时c所依赖的属性的get方法既收集了computedWatcher,又收集了renderWatcher。
到现在为止,组件的创建周期完成,当操作页面,有reactive属性的set方法被调用,发生组件重新渲染,进入更新周期的时候,分两种情况
第一,这个reactive属性被c所依赖,那么它的set方法被调用会导致computedWatcher的update方法被调用,因为lazy为true,所以直接设置dirty为true,并不computedWatcher被放入异步微任务队列。
而renderWatcher的的update方法会进入异步微任务队列,然后引起组件的重新渲染,在这个过程中,调用c的get方法,调用上面说的重写的computedGetter方法会发现watcher.dirty为true(宏任务中完成,所以时间早于微任务),
所以通过evaluate方法重新计算一次watcher.value,返回。这个是符合逻辑的,因为c所依赖的属性改变了。
第二种情况,就是引发这次重新渲染的属性并没有被c所依赖,那么它并没有收集computedWatcher,只有renderWatcher,那么render的时候获取c的属性,调用computedGetter方法的时候发现dirty为false,直接获取watcher.value而不是调用evaluate
重新计算一次。