vue源码分析(三)>>:computed
看完watch在看看computed时怎么实现的:
step1:用法
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>computed</title> </head> <body> <div id="app"> <p>欢迎:{{userName}}</p> <p>欢迎:{{userNameM()}}</p> <button @click="handleClick">click</button> </div> <script src="../../dist/vue.js"></script> <script> let vm = new Vue({ el:'#app', data:{ user:{ firstName:'张', lastName:'三丰' }, }, methods: { handleClick(){ this.user.firstName = "周"; }, userNameM(){ return this.user.firstName + this.user.lastName; } }, computed: { userName(){ return this.user.firstName + this.user.lastName; } }, }) </script> </body> </html>
可以把计算属性看成一般的属性用,watch里边也能监听到userName的变化;另外用方法也能实现同样的效果,先写上。
step2:源码实现
还是从_init开始看起,到initState,在这里在initWatch之前,走initComputed方法,传参第一个参数是:vm,第二个参数是:vm.$options.computed;第一个参数就是当前示例对象,第二个参数就是我们所写的computed对象,看这个方法:
// 初始化computed
function initComputed (vm, computed) {
debugger
// $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.
// 定义watcher
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)) {// 如果key和其它方法或者属性冲突就抛异常
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);
}
}
}
}
然后创建Watcher,这和watch时候的一样了就,下边走defineComputed:就是创建它的getter
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);
}
function createComputedGetter (key) {
debugger
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
}
}
}
function createGetterInvoker(fn) {
return function computedGetter () {
return fn.call(this, this)
}
}
至此:初始化工作就完成了。
step3:触发逻辑
当我们获取userName时候就会触发他的get方法就是上面 computedGetter方法,debugger已经打上了,debug走一下:
function createComputedGetter (key) {
return function computedGetter () {
debugger
var watcher = this._computedWatchers && this._computedWatchers[key];// 获取watcher对象
if (watcher) {
if (watcher.dirty) {// 如果dirty为true
watcher.evaluate();//获取数据
}// 否则就直接返回
if (Dep.target) {
watcher.depend();
}
return watcher.value
}
}
}
断点看到,界面上渲染时候获取也就是第一次获取,这个dirty是true就去获取了,我第二次获取时候,dirty是false直接返回的watcher.value,接下来看看watcher.evaluate()方法:
/**
* Evaluate the value of the watcher.
* This only gets called for lazy watchers.
*/
Watcher.prototype.evaluate = function evaluate () {
console.log("Watcher evaluate");
this.value = this.get();// 获取值 赋值给当前watcher
this.dirty = false;// 把当前dirty设为false
};
/**
* Evaluate the getter, and re-collect dependencies.
*/
Watcher.prototype.get = function get () {
debugger
console.log("Watcher get");
pushTarget(this);
var value;
var vm = this.vm;
try {
value = this.getter.call(vm, vm);// 走getter方法获取值
} catch (e) {
if (this.user) {
handleError(e, vm, ("getter for watcher \"" + (this.expression) + "\""));
} else {
throw e
}
} finally {
// "touch" every property so they are all tracked as
// dependencies for deep watching
if (this.deep) {// 如果是深度监听
traverse(value);
}
popTarget(); // 对应 pushTarget(this);
this.cleanupDeps();
}
return value
};
现在分析下这个dirty,当user里边的firstName或lastName改变时候会触发watcher的update方法,会改变这个dirty为true,在随后获取时候就不会直接返回wather.value了;这里就体现出和用方法实现起来的区别了,区别就是这个computed会缓存,这个dirty逻辑就是缓存。
暂时就这样
over!