computed 示例
var vm = new Vue({
data: {
firstName: 'Foo',
lastName: 'Bar'
},
computed: {
fullName: function() {
return this.firstName + ' ' + this.lastName;
}
}
});
- 初始化 computed watcher 实例的构造函数
constructor (
vm: Component,
expOrFn: string | Function,
cb: Function,
options?: ?Object,
isRenderWatcher?: boolean
) {
// 针对上例,如果computed存在,不会立即求值,而去建一个Dep实例
// 当render函数执行访问到this.firstName的时候,就触发的计算属性的getter
// 这使会拿到计算属性的watcher,然后执行 watcher.depend()
if (this.computed) {
this.value = undefined
this.dep = new Dep()
} else {
this.value = this.get()
}
}
- 执行 watcher.depend()
// 这步中Dep.target是渲染watcher
depend () {
if (this.dep && Dep.target) {
// 所以this.dep.depend()相当于渲染watcher订阅了整个 computed watcher的变化
this.dep.depend()
}
}
- 执行 watcher.evaluate() 去求值
// 该步执行完Dep.target就是computed watcher
evaluate () {
// 判断 this.dirty,如果为true
if (this.dirty) {
// 通过 this.get() 求值
// 求值过程中会执行 value = this.getter.call(vm, vm)
// this.getter.call(vm, vm)就会执行计算属性的getter函数
// 在该例子中,就是执行this.firstName + ' ' + this.lastName
// 因为this.firstNameh和this.lastName是响应式对象,进而就会触发他俩的getter
// 再进而就把自身持有的dep添加到正在计算的watcher中,这时候Dep.target就是这个 computed watcher
this.value = this.get()
// 并且把this.dirty置为false
this.dirty = false
}
// 拿到计算属性对应的值
return this.value
}
- computed watcher 依赖的数据变化后会触发 setter 过程,通知所有订阅它变化的 watcher,执行 watcher.update()
// w
if (this.computed) {
// 如果没有订阅计算属性变化的watcher,采用lazy模式
// 只有当下次访问这个计算属性的时候才会重新求值
if (this.dep.subs.length === 0) {
this.dirty = true;
} else {
// 针对上列,是有订阅计算属性变化的watcher的,所以会执行这步
this.getAndInvoke(() => {
this.dep.notify();
});
}
} else if (this.sync) {
this.run();
} else {
queueWatcher(this);
}
this.getAndInvoke(() => {
this.dep.notify()
})
// getAndInvoke会对比新旧值,只有数值有变化,才会执行this.dep.notify(),触发渲染watcher重新渲染
getAndInvoke (cb: Function) {
const value = this.get()
if (
value !== this.value ||
// Deep watchers and watchers on Object/Arrays should fire even
// when the value is the same, because the value may
// have mutated.
isObject(value) ||
this.deep
) {
const oldValue = this.value
this.value = value
this.dirty = false
if (this.user) {
try {
cb.call(this.vm, value, oldValue)
} catch (e) {
handleError(e, this.vm, `callback for watcher "${this.expression}"`)
}
} else {
cb.call(this.vm, value, oldValue)
}
}
}
computed 是什么
computed 实际上是一个 computed watcher
在初始化 computed 的时候,会触发渲染 watcher 订阅 computed ,然后在求值的过程中,会执行计算属性的 getter 函数,又因为求值的两个变量是响应式对象,所以又触发它俩的 getter,把它俩自身持有 dep 添加到 watcher 中,如果计算属性依赖的变量有变化,会触发渲染 watcher 的重新渲染,如果依赖的变量有变化,但是新值和旧值相等,是不会触发重新渲染,只有最终计算的值发生变化,才会执行 this.dep.notify(),触发渲染 watcher 重新渲染
computed 源码
- initComputed
// src/core/instance/state.js
const computedWatcherOptions = { computed: true };
function initComputed(vm: Component, computed: Object) {
// 先创建vm.computedWatchers为空对象
const watchers = (vm._computedWatchers = Object.create(null));
// 判断是否是ssr
const isSSR = isServerRendering();
// 然后对computed属性进行遍历
for (const key in computed) {
// 拿到计算属性每一个userDef
const userDef = computed[key];
// 试着去获取每一个userDef的getter函数
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);
}
// 如果不是ssr,就新建一个Watcher
if (!isSSR) {
// 为每一个getter创建一个Watcher
// 这个Watcher和渲染watcher不同,在代码开头定义了const computedWatcherOptions = { computed: true }
watchers[key] = new Watcher(vm, getter || noop, noop, computedWatcherOptions);
}
// 对key进行判断
if (!(key in vm)) {
// 如果不是vm的属性,则用defineComputed
defineComputed(vm, key, userDef);
} else if (process.env.NODE_ENV !== 'production') {
// 如果已经在vm.$data或者在props中,则报出已经存在的警告
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);
}
}
}
}
- defineComputed
// src/core/instance/state.js
// 利用Object.defineProperty给计算属性对应的key值添加getter和setter
// 只要计算属性的key有set,那么setter是计算属性的一个对象,否则就是一个空函数
export function defineComputed(target: any, key: string, userDef: Object | Function) {
const shouldCache = !isServerRendering();
if (typeof userDef === 'function') {
sharedPropertyDefinition.get = shouldCache ? createComputedGetter(key) : userDef;
sharedPropertyDefinition.set = noop;
} else {
sharedPropertyDefinition.get = userDef.get ? (shouldCache && userDef.cache !== false ? createComputedGetter(key) : userDef.get) : noop;
sharedPropertyDefinition.set = userDef.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);
}
- createComputedGetter
// src/core/instance/state.js
function createComputedGetter(key) {
// 返回一个computedGetter函数,这个就是计算属性的getter
return function computedGetter() {
const watcher = this._computedWatchers && this._computedWatchers[key];
if (watcher) {
watcher.depend();
return watcher.evaluate();
}
};
}
确认评论邮箱
邀请制评论