六、计算属性
在initState函数中,会调用initComputed来初始化computed,initComputed函数首先通过Object.create(null)创建一个没有原型对象和构造函数的空对象,然后遍历传入的第二个参数
computed拿到computed对象中的每一个值,computed有两种写法,一种是直接写一个求值函数,另一种是写一个对象,其中有get等于一个求值函数,所以这里通过typeof进行判断,如果是一个函数,那么让getter等于这个函数,否则等于他的get。之后通过new Watcher的方式,定义一个computed watcher。最后调用defineComputed方法,defineComputed函数中shouldCache的值首先定义true(浏览器环境),然后通过typeof来判断传入的userDef的类型,之后定义sharedPropertyDefinition的get为createComputedGetter(key), 执行createComputedGetter(key),createComputedGetter函数返回了computedGetter函数,最终返回computed wathcer的value。如果有set,那么直接让sharedPropertyDefinition 的set等于传入的userDef的set,最终通过Object.defineProperty(target, key, sharedPropertyDefinition)把computed绑定到vm上。所以传入的computed的写法最终也是通过Object.defineProperty进行了数据劫持,在访问的时候,通过get去执行对应的computed watcher的逻辑
// src/core/instance/state.js
function initComputed (vm: Component, computed: Object) {
// $flow-disable-line
const watchers = vm._computedWatchers = Object.create(null)
// computed properties are just getters during SSR
const isSSR = isServerRendering()
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) {
...
}
if (!isSSR) {
// create internal watcher for the computed property.
watchers[key] = new Watcher(
vm,
getter || noop,
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)
} else if (process.env.NODE_ENV !== 'production') {
...
}
}
}
export function defineComputed (
target: any,
key: string,
userDef: Object | Function
) {
const shouldCache = !isServerRendering()
if (typeof userDef === 'function') {
sharedPropertyDefinition.get = shouldCache
? createComputedGetter(key)
: createGetterInvoker(userDef)
sharedPropertyDefinition.set = noop
} else {
sharedPropertyDefinition.get = userDef.get
? shouldCache && userDef.cache !== false
? createComputedGetter(key)
: createGetterInvoker(userDef.get)
: noop
sharedPropertyDefinition.set = userDef.set || noop
}
if (process.env.NODE_ENV !== 'production' &&
sharedPropertyDefinition.set === noop) {
...
}
Object.defineProperty(target, key, sharedPropertyDefinition)
}
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
}
}
}
关键的两步,第一步,通过 watchers[key] = new Watcher( vm, getter || noop, noop, computedWatcherOptions )给 vm._computedWatchers进行对应的赋值。在new Watcher的过程中,第一个参数传入vm实例,第二个参数传入getter也就是computed的get函数,第三个参数传空也就是说compoted watcher没有回调函数,第四个参数传入computedWatcherOptions也就是 { lazy: true },执行watcher的constructor,把当前的computed watcher push到vm._watcher,之后this.lazy 和 this.dirty为true,this.getter 等于传入的computed函数,最后this.value = this.lazy?undefined:this.get()在首次this.lazy为true,所以首次,this.value为undefined也就是说并不会直接求值
// src/core/observer/watcher.js
/**
* A watcher parses an expression, collects dependencies,
* and fires callback when the expression value changes.
* This is used for both the $watch() api and directives.
*/
export default class Watcher {
vm: Component;
expression: string;
cb: Function;
id: number;
deep: boolean;
user: boolean;
lazy: boolean;
sync: boolean;
dirty: boolean;
active: boolean;
deps: Array<Dep>;
newDeps: Array<Dep>;
depIds: SimpleSet;
newDepIds: SimpleSet;
before: ?Function;
getter: Function;
value: any;
constructor (
vm: Component,
expOrFn: string | Function,
cb: Function,
options?: ?Object,
isRenderWatcher?: boolean
) {
this.vm = vm
if (isRenderWatcher) {
...
}
...
// options
if (options) {
...
this.lazy = !!options.lazy
...
} else {
...
}
...
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.expression = process.env.NODE_ENV !== 'production'
? expOrFn.toString()
: ''
// parse expression for getter
if (typeof expOrFn === 'function') {
this.getter = expOrFn
} else {
this.getter = parsePath(expOrFn)
if (!this.getter) {
this.getter = noop
process.env.NODE_ENV !== 'production' && warn(
`Failed watching path: "${expOrFn}" ` +
'Watcher only accepts simple dot-delimited paths. ' +
'For full control, use a function instead.',
vm
)
}
}
this.value = this.lazy
? undefined
: this.get()
}
}
那当在渲染的过程中,访问到了coumputed的时候,就会触发computedGetter函数,首次访问watcher.dirty为true,会执行watcher.evaluate(),watcher的evaluate函数,首先会执行this.value = this.get()去调用get函数进行求值,也就是会让watcher的value等于getter函数(之前传入的computed get函数)的返回值,执行get之后,会判断是否有Dep.target,在此处的Dep.target为渲染watcher,他会执行watcher.depend(),watcher.depend()会对当前computed的this.deps数组中的值调用depend,也就是调用Dep的depend。this.deps在执行evaluate的时候,会触发对应响应式的依赖收集,也就是说当前的coumoted watcher中的deps已经存储了依赖的数据的dep。那么在执行computed watcher的depend的时候,会把这些dep执行depend方法,当前的Dep.target是渲染watcher,也就是说渲染wathcer订阅了computed的依赖
// src/core/instance/state.js
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
}
}
当依赖的值发生了改变,也就是触发了dep.notify(),他会先拿到computed wathcer,首次进入watcher的update会先把this.dirty重新设置为true,在之后的notify会触发渲染watcher的queueWatcher进行页面的更新。重新获取渲染watcher后,再次调用之前的逻辑,进行computed watcher的初始化。当前的vue版本是2.6.12。为什么说computed是有缓存的,是因为当调用computedGetter函数,watcher.dirty为true才会重新调用watcher.evaluate()进行求值,当执行过一次watcher.evaluate()后,watcher.dirty为false,则不会再次调用watcher.evaluate()而是直接return watcher.value