前言
上篇文章就new Vue()过程中的基本处理流程有了了解,并对data和挂载部分进行了分析,本次就针对于computed进行具体的分析。
计算属性computed的提出三个问题点:
- Vue对于computed的具体处理
- computed与data有何区别
- computed是如何收集依赖的
computed
computed的处理逻辑都是由initComputed而起,具体的处理逻辑如下:
从上面可知,如同每个data会有对应的Watcher实例用于实现data响应式:
每个computed也会有一个Watcher对象实例来实现computed的响应式
当computed中定义的属性不与data和props中同名,则会调用defineComputed来定义computed,实际上该函数实现类似defineReactive功能:
defineReactive是实现data响应式的核心,即Object.defineProperty拦截data中数据
defineComputed亦是如此,是实现computed响应式的核心,即Object.defineProperty拦截computed中属性
具体看看defineComputed的处理逻辑:
从上面可知,defineComputed就是使用Object.defineProperty来实现响应式的,针对浏览器环境需要构建特殊处理getter部分,即使用createComputedGetter来构建getter。
实际上createComputedGetter内部构建的getter实际会调用Watcher对象的depend和exaluate方法,具体处理如下:
function createComputedGetter (key) {
return function computedGetter () {
var watcher = this._computedWatchers && this._computedWatchers[key];
if (watcher) {
watcher.depend();
return watcher.evaluate()
}
}
}
这就是new Vue过程中关于computed整个处理过程,可能要问:
- 没有关于computed与data的区别啊?
- 没有computed如何收集依赖的啊?
实际上这两个问题都是在Watcher相关的处理:new Watcher、watcher、depend、evaluate这些过程,接下来就computed相关来看上面三个过程的具体处理。
Watcher处理computed相关
首先是通过Watcher创建监听对象,涉及到computed如下:
// 标记是否是计算属性
this.computed = !!options.computed;
this.dirty = this.computed;
if (this.computed) {
this.value = undefined;
// 建立依赖对象
this.dep = new Dep();
}
实际上关于computed,new Watcher时候主要就是上面的处理,那是如何收集依赖的呢?
实际上这部分就是computed的getter部分来实现的,从之前分析中可以得到:getter中实际上会调用Watcher实例对象的depend和evaluate。
Watcher.prototype.depend
该实例方法内部的处理如下:
if (this.dep && Dep.target) {
// 实际上调用Dep对象的depend实例方法
this.dep.depend();
}
其实在上篇文章中就分析Dep对象的depend方法的具体处理,如下:
Dep.target.addDep(this)调用Watcher对象实例方法addDep
Watcher的addDep实例方法的作用是:
建立Dep与Watcher的联系,即Dep中subs属性保存Watcher对象
Watcher.prototype.evaluate
该实例方法的具体处理是执行watcher实例的get方法:
if (this.dirty) {
this.value = this.get();
this.dirty = false;
}
return this.value
实际上该实例方法是computed收集依赖和缓存计算属性的触发点
this.get()实际是getter函数(就是开发者定义的计算属性的函数或get函数)的执行,此时获取data的变量会触发data的getters方法,即watcher收集data依赖的过程,在上篇文章中就分析了这个过程中会调用dep.depend,将watcher与dep建立联系
上面就是computed如何收集依赖,最后的收集与watcher收集data依赖是相同的处理,通过上面的步骤与computed Watcher建立联系。
再来说说computed的缓存效果,实现缓存效果实际上就是this.dirty控制的:
this.dirty的作用,当this.dirty为true或调用getter方法,触发data的getter获取当前值计算,this.dirty为false就会直接返回当前Watcher实例中value值
当computed依赖发生变化,就会将触发setter方法,这边的处理在上文中有具体分析,实际上是会触发:
setter -> dep.notify() -> Watcher实例的update()
而当依赖发生变化,实际上会触发所有包含该Dep对象的Watcher实例对象的update()实例方法。
update这边所涉及的处理比较复杂,现在只要记住这边会重新计算computed值以及重新渲染视图即可,之后会专门介绍这块的处理。
总结
通过上面的分析,可知下面几点:
- 每个computed相对应对应一个Watcher实例对象
computed收集依赖实际上是在其getter中触发的
computed getter -> 依赖的getter -> dep.depend建立了当前dep与Watcher的联系
computed与data的区别是存在缓存,缓存的控制实际上是Watcher中dirty属性来控制的,false时就取缓存中的值