computed:计算属性,一般用于一个变量依赖多个变量的变化,或者是需要经过一些处理。
- 值是响应式的
- 数据会进行缓存
下面是相对于源码的解析,注释中含有解释:
function initState (vm) {
vm._watchers = [];
var opts = vm.$options;
if (opts.props) { initProps(vm, opts.props); }
if (opts.methods) { initMethods(vm, opts.methods); }
if (opts.data) {
initData(vm);
} else {
observe(vm._data = {}, true /* asRootData */);
}
if (opts.computed) { initComputed(vm, opts.computed); }
if (opts.watch && opts.watch !== nativeWatch) {
initWatch(vm, opts.watch);
}
}
1、initComputed
(vm, vm.$options.computed
)
- 生成一个空对象
_computedWatchers
,并挂载到vue实例中 - 获取
computed
的getter
(要么为computed
定义值的函数本身,或者对象中必须存在get
方法) - 为
computed
对象每一个key
分配一个watcher
实例,并挂载到_computedWatchers
- 判断每一个
key
和data
或者prop
不会重名,否则报错 - 执行
defineComputed
var computedWatcherOptions = { lazy: true };
function initComputed (vm, computed) {
//①生成一个空对象_computedWatchers,并挂载到vue实例中
var watchers = vm._computedWatchers = Object.create(null);
//②获取computed的getter(要么为computed定义值的函数本身,或者对象中必须存在get方法)
for (var key in computed) {
var userDef = computed[key];
var getter = typeof userDef === 'function' ? userDef : userDef.get;
if (process.env.NODE_ENV !== 'production' && getter == null) {
warn(
("Getter is missing for computed property \"" + key + "\"."),
vm
);
}
//③为computed对象每一个key分配一个watcher实例,并挂载到_computedWatchers
//与其他地方使用watcher不同的是传入了配置参数{lazy: true},目的是为了实例化的时候不会立即求值,而是在访问到它的地方才去求值
if (!isSSR) {
watchers[key] = new Watcher(
vm,
getter || noop,
noop,
computedWatcherOptions // {lazy: true}
);
}
// ④执行defineComputed
if (!(key in vm)) {
defineComputed(vm, key, userDef);
} else if (process.env.NODE_ENV !== 'production') {
// ⑤判断每一个key和data或者prop不会重名,否则报错
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);
}
}
}
}
2、defineComputed
(vm, key, userDef),userDef
是function或者object
- 通过
defineProperty()
给computed
的每一个key
重新设置get
和set
方法 - 判断
key
为funcition
,那么set
去设置为noop
,get
通过cache
判断执行createComputedGetter()
或者createGeeterInvoker()
function defineComputed (
target,
key,
userDef // 计算属性对应的值
) {
// 浏览器环境下为true
var 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);
}
3、createComputedGetter(key)
,实现缓存的关键函数
- 从
computedWatchers
中取出对应的watch
实例,返回watch.value
值。 - 需要通过
watch.dirty
(数据依赖变化情况),执行watcher.evaluate()
重新计算获取值
function createComputedGetter (key) {
return function computedGetter () {
var watcher = this._computedWatchers && this._computedWatchers[key];
if (watcher) {
if (watcher.dirty) {
watcher.evaluate();
}
if (Dep.target) {
watcher.depend();
}
return watcher.value
}
}
}
具体实例来说就是:当该render
函数执行访问到computed
所依赖的值的时候,就会触发渲染computed watcher
的getter
方法,执行watcher.depend()
。
depend(){
if(this.dep && Dep.target) {
//渲染 watcher 订阅了这个 computed watcher 的变化。
this.dep.depend()
}
}
evaluate () {
if(this.dirty) {
this.value = this.get()
this.dirty = false
}
return this.value;
}
4、computed watcher
对象(和渲染watcher不同)
- 初始化:
lazy
赋值给dirty
,所以会执行一次evaluate()
,执行get
更改value
值,然后将dirty
设置为false,后续访问改值不会重新进行计算,直接取value - 收集响应式依赖,后续依赖数据发生变化,就会执行
watch.update
方法,此时把dirty
设置为true
,则会调用get()
方法计算更新value
computed watcher
并不会立马求值,同时持有一个 dep 实例。
constructor (
vm: Component,
expOrFn: string | Function,
cb: Function,
options?: ?Object,
isRenderWatcher?: boolean
) {
// ...
if (this.computed) {
this.value = undefined
this.dep = new Dep()
} else {
this.value = this.get()
}
}
**总结:**computed实现主要是在vue实例下生成一个_computedWatchers对象,后续所有访问都是通过该对象。对象中每一个键值都是一个watcher,访问时watcher返回value的值,通过dirty去控制是否重新计算,每次依赖变化都会触发watcher的update()改变dirty的值,下一次访问的时候重新计算value,否则直接返回上一次的value。