new Vue({
el: '#app',
data: {
baseData: 1,
},
computed: {
computedData: function () {
return this.baseData + 1
}
},
methods: {
changeData() {
this.baseData = 2;
}
}
})
如上,计算属性computedData依赖于this.baseData。
一、计算属性首次执行
1、initComputed
在执行new Vue的过程中,执行到initState,当满足if (opts.computed) { initComputed(vm, opts.computed)}时,执行initComputed初始化计算属性。
const computedWatcherOptions = { lazy: true }
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) {
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
)
}
// 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') {
if (key in vm.$data) {
warn(`The computed property "${key}" is already defined in data.`, vm)
} else if (vm.$options.props && key in vm.$options.props) {
warn(`The computed property "${key}" is already defined as a prop.`, vm)
}
}
}
}
通过循环的方式在const watchers = vm._computedWatchers = Object.create(null)实例化Watcher类,watchers[key] = new Watcher( vm, getter || noop, noop, computedWatcherOptions )
通过传入参数const computedWatcherOptions = { lazy: true }标明当前watcher是计算属性。
实例化完计算属性后,如果当前的key不在vm实例上,则响应式处理当前计算属性的key:
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) {
sharedPropertyDefinition.set = function () {
warn(
`Computed property "${key}" was assigned to but it has no setter.`,
this
)
}
}
Object.defineProperty(target, key, sharedPropertyDefinition)
}
isServerRendering为false,所以shouldCache为true,get的时候执行的是函数createComputedGetter:
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
}
}
}
2\
(1)watcher.evaluate()手动watcher计算
当watcher.dirty为true的时候,执行watcher.evaluate()手动计算:
/**
* Evaluate the value of the watcher.
* This only gets called for lazy watchers.
*/
evaluate () {
this.value = this.get()
this.dirty = false
}
这里执行到this.get,进而执行到computer watcher的回调函数:
function () {
return this.baseData + 1
}
这里访问到了this.baseData,会触发this.baseData的get,进而在其锁定的发布者dep的subs中推入的计算属性computer watcher。
(2)watcher.depend()手动依赖收集
当Dep.target为true的时候,执行watcher.depend()手动依赖收集:
/**
* Depend on all deps collected by this watcher.
*/
depend () {
let i = this.deps.length
while (i--) {
this.deps[i].depend()
}
}
通过手动的方式,将当前watcher订阅的发布者deps进行循环,收集当前正在计算的watcher,这个例子中指的是渲染watcher
4、依赖收集总结
当执行_render获取vNode的过程中会访问到this.computedData,触发到get方法,即createComputedGetter中返回的computedGetter:
function computedGetter () {
const watcher = this._computedWatchers && this._computedWatchers[key]
if (watcher) {
if (watcher.dirty) {
watcher.evaluate()
}
if (Dep.target) {
watcher.depend()
}
return watcher.value
}
}
(1)watcher.evaluate
watcher.evaluate的过程中会执行value = this.getter.call(vm, vm),进而访问到this.baseData的数据,进而触发get,执行dep.depend()计算属性依赖收集操作:
dep的subs中推入computer watcher;
(2)watcher.depend
watcher.depend的过程会触发this.deps[i].depend()收集的过程,例子中的this.baseData锁定的dep可以再次收集到渲染依赖:
dep的subs中再次推入render watcher;
经过watcher.evaluate和watcher.depend的执行,this.baseData的依赖subs中就有computer watcher和render watcher。
二、计算属性依赖数据变化
当this.baseData变化时,会执行dep.notify方法:
Dep.prototype.notify = function notify () {
// stabilize the subscriber list first
var subs = this.subs.slice();
if (process.env.NODE_ENV !== 'production' && !config.async) {
// subs aren't sorted in scheduler if not running async
// we need to sort them now to make sure they fire in correct
// order
subs.sort(function (a, b) { return a.id - b.id; });
}
for (var i = 0, l = subs.length; i < l; i++) {
subs[i].update();
}
};
这里dep的subs有两个元素,浅拷贝var subs = this.subs.slice()的方式获取到subs,循环执行update方法:
/**
* Subscriber interface.
* Will be called when a dependency changes.
*/
Watcher.prototype.update = function update () {
/* istanbul ignore else */
if (this.lazy) {
this.dirty = true;
} else if (this.sync) {
this.run();
} else {
queueWatcher(this);
}
};
第一次循环computer watcher的this.lazy为true,因此this.dirty = true;
第二次循环render watcher会通过异步的方式执行queueWatcher(this),等所有的同步结束后,会执行到render watcher的执行,即:
updateComponent = () => {
vm._update(vm._render(), hydrating)
}
通过_render的方式获取vNode的时候又会访问computedData,进而执行计算属性响应式处理过程中的get,
function computedGetter () {
const watcher = this._computedWatchers && this._computedWatchers[key]
if (watcher) {
if (watcher.dirty) {
watcher.evaluate()
}
if (Dep.target) {
watcher.depend()
}
return watcher.value
}
}
这里依然会执行到计算属性的重新计算和依赖的收集,收集的方法会执行到:
/**
* Add a dependency to this directive.
*/
Watcher.prototype.addDep = function addDep (dep) {
var id = dep.id;
if (!this.newDepIds.has(id)) {
this.newDepIds.add(id);
this.newDeps.push(dep);
if (!this.depIds.has(id)) {
dep.addSub(this);
}
}
};
在首次渲染的时候已经执行过依赖收集,因此this.depIds中已经存在id,这里跳过执行。
小结:
计算属性也是
Watcher类的实例,在访问所依赖的数据时被收集,在依赖的数据发生变化时依然触发dep.notify,computer watcher主要修改this.dirty = true,使得访问计算属性的时候可以手动计算修改后的值。