computed

116 阅读4分钟

概括

计算属性是通过观察data中的属性的变化而变化的。其实,这里涉及到三方,一是被观察的data中的属性,二是计算属性,三是页面。计算属性是充当媒介,让被观察的data中的属性和页面建立了联系。当被观察的data中的属性被修改时,会通知页面更新,更新就会获取计算属性,从而触发计算属性的求值。

主要的两个问题是:1.计算属性是如何与被观察的data中的属性及页面建立联系的。2.被观察的属性更新后又是如何通知到页面和计算属性的更新的。

流程

初始化

computed的初始化在data的后面,就是在vue实例化的init方法中的initState()里进行的。具体方法是initComputed()。下面来看下初始化具体做了哪些工作。

首先,我们知道,计算属性其实就是一些观察者,观察他们依赖的属性变化而变化。所以,在初始化中,遍历计算属性,为它们每一个都new Watcher()。然后把这些计算属性的watcher实例放入到vm._computedWatcher中。这个_computedWatcher中是通过Object.create(null)创建出的。

计算属性在创建watcher实例时,并没有立即求值,只是在给watcher实例中的lazy和dirty属性设置为true。

接着,就调用defineComputed方法,为每个计算属性设置get和set方法。并把这些添加给当前vue实例下。

以上计算属性初始化完成。简言之,为每个计算属性创建watcher实例,并为每个计算属性设置get和set方法。

建立联系

当解析页面并对页面求值时,如果要获取计算属性,必定会触发它的get方法。这时会根据计算属性的属性名找到初始化时创建的watcher,然后判断watcher.dirty是否为true,否的话直接返回watcher.value,是的话调用watcher实例中的evaluate方法。这个方法做了两件事,一是调用watcher中的get()方法获取计算属性的值,二是把当前计算属性watcher实例的dirty设置为false。

watcher实例中的get方法,会先把当前watcher实例赋值给变量Dep.target,然后获取计算属性的值,其实就是调用计算属性对应的方法,这时,在执行这个方法时,会通过已有的data中的属性获取,这些data中的属性又是响应式的,所以就会调用到它们的get方法去收集watcher,因为前面Dep.target的值已经设置成当前计算属性的watcher了,所以,这些data中的属性就会把当前计算属性的watcher实例收集到各自的dep中。这样,计算属性和被观察的data中的属性就建立起了联系。执行完方法后,把返回值再赋值给watcher.value中。最后再把Dep.target的值还原为之前的值,其实就是最初页面的watcher。

watcher的get方法执行完毕后,在把当前计算属性的watcher实例中的dirty属性设置为false。证明当前计算属性已经被计算过并缓存了,下次取值时可以直接取watcher.value不需要再调用了watcher.evaluate方法进行计算取值了。

执行完evaluate方法后,通过watcher.deps(这个deps是在cleanupDeps中设置的)获取到当前计算属性watcher所依赖的所有dep,然后调用dep的depend方法把Dep.target的值,也就是页面watcher添加到dep中,这样,就把当前计算属性所依赖的所有data中的属性,都与当前页面建立了联系。

更新

当被观察的data中的某个属性被修改后,就会调用该属性的set方法,在执行set方法中,会调用其dep中的notiyfy方法发布更新,这时该属性dep中的所有watcher实例都会调用update方法进行更新,在update方法中会判断,如果watcher.lazy是true,证明是计算属性的watcher,就会把dirty设置为true,证明依赖项更新了,该计算属性要重新求值了。如果是false就是普通页面,当然,这些页面里也是包含以计算属性为媒介创建联系的watcher,当这些watcher更新求值时,必定会获取计算属性,这时又会触发获取计算属性的get方法,可以进行重新求值了。