关于computed
前言:
前言就是有了前几篇的基础对于vue相应式原理的初步了解之后,再去看这两个东西会方便很多。写这篇文章是为了一个梳理,还有一些其他的原因,年底再说。
先看computed
computed是在initState的时候,并且在初始化data之后,进行初始化的,来看看初始化的时候它干了什么:
function initComputed (vm, computed) {
// $flow-disable-line
var watchers = vm._computedWatchers = Object.create(null);
// computed properties are just getters during SSR
var isSSR = isServerRendering();
for (var key in computed) {
var userDef = computed[key];
var getter = typeof userDef === 'function' ? userDef : userDef.get;
if (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 (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);
}
}
}
}两参数vm computed就是当前的vm组建和实例上的computed属性,说的通俗点就是new Vue的时候写的那个computed,然后我直接关注for in 里的代码:这里拿到computed上的key和对应的value之后,第一步是取得computed里key对应的value,这里value可能会是一个函数,一个对象,如果是函数就直接取,如果是对象就取他的get方法,然后会为当前的key生成一个watcher:
watchers[key] = new Watcher(
vm,
getter || noop,
noop,
computedWatcherOptions
);
//computedWatcherOptions 等于 var computedWatcherOptions = { lazy: true };这里啊,这个lazy如果你对watcher构造函数的源码模糊了,建议重新打开一个窗口看看,computed从源码角度来讲,为什么data里的值变了之后才会跟着变呢,是因为这个地方,
第二步是判断当前key是否与props或data里边的key重复,有则警告,没有就调用defineComputed:
function defineComputed (
target,
key,
userDef
) {
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 (sharedPropertyDefinition.set === noop) {
sharedPropertyDefinition.set = function () {
warn(
("Computed property \"" + key + "\" was assigned to but it has no setter."),
this
);
};
}
Object.defineProperty(target, key, sharedPropertyDefinition);
}
//这里
var sharedPropertyDefinition = {
enumerable: true,
configurable: true,
get: noop,
set: noop
};直接看最后一句,vue的老套路了,劫持一个对象的属性的get set,还有一层作用是执行vm.key的时候把这个操作代理到了当前定义的get上,其实computed的set不怎么常用,那来看看这里的get是什么:shouldChache是isServerRendering的执行结果取反的结果,isServerRendering 字如其名自然跟服务端渲染相关,看源码的话,当在服务端运行的时候此值才有可能取真值,那这里他就是false,然后,shouldChache自然取true,接着走,然后computed对应的value一般为function,所以这里执行createComputedGetter:
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
}
}
}这里实际是把key闭了一个包,返回的函数呢,就是sharedPropertyDefinition的get,所以,此后某个地方访问vm.key的时候这个闭包函数就会执行,再看一下这个函数执行的细节,第一步取得当前computed里key对应的watcher实例,然后判断watcher.dirty是否为true,这个dirty和上边提到的lazy在实例化watcher的时候用的是同一个值,在初始化所有computed的时候他们都为true,因此第一次渲染的时候,这个地方会执行到watcher.evaluate():
Watcher.prototype.evaluate = function evaluate () {
this.value = this.get();
this.dirty = false;
};evaluate很简单,计算value,标记dirty为false,这里的get前面我们也应该讲过:他做了一件事情就是将当前watcher pushTarget,然后调用watcher实例的getter方法,getter方法就是初始化watcher的时候传进去的那个getter,那这个getter里的东西呢,我们注意到computed里一般都会访问到当前data的get方法,而前几篇讲了在get方法中,我们就能收集到当前的watcher,因此此时就会完成依赖收集。然后computed就定义完毕了。

浙公网安备 33010602011771号