计算属性
计算属性的形式
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
|
computed:{ fullname(){ return this.firstname + this.lastname }, fullname:{ get(){ return this.firstname + this.lastname }, set(newVal){ console.log(newVal) } } }
|
特性
计算属性具有缓存,当多次调用时,如果值没有改变则只会计算一次。只有依赖值变了才会重新执行用户的方法。
计算属性是一个defineProperty,计算属性也是一个watcher。
初始化computed
计算属性也是配置在新建Vue实例的参数中,所以我们需要在state.js中中取到,再进行处理。
我们额外新建一个initComputed方法,在state.js中执行。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
|
if(opts.computed){ initComputed(vm); }
function initComputed(vm){ const computed = vm.$options.computed; const watchers = vm._computedWatchers = {};
for(let key in computed){ let userDef = computed[key]; let fn = typeof userDef === 'function' ? userDef : userDef.get; watchers[key] = new Watcher(vm,fn,{lazy:true}); defineComputed(vm,key,userDef); } }
|
为计算属性添加响应式
计算属性本质上也是一个响应式数据,能在页面中使用花括号表达式直接使用。所以这里也是使用Object.defineProperty()绑定响应式。
其中的set无须处理,本质是依赖的data中的值,data本身set会触发赋值重新渲染逻辑,我们只考虑重新渲染时加上计算属性watcher即可。
get函数需要单独处理,计算属性具有缓存功能,当计算值未发生变化时,多次使用也只会调用一次get,我们需要通过维护一个dirty属性去判断是否需要重新计算。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
| function defineComputed(target,key,userDef){ const setter = userDef.set || (()=>{});
Object.defineProperty(target,key,{ get: createComputedGetter(key), set: setter, }) }
function createComputedGetter(key){ return function(){ const watcher = this._computedWatchers[key]; if(watcher.dirty){ watcher.evaluate(); }
if(Dep.target){ watcher.depend(); } return watcher.value; } }
|
兼容watcher
从前边代码可以看出,new Watcher时传入了一个lazy属性。我们在watcher内部新增一个dirty属性,取lazy值,默认第一次watcher则为脏,需要重新计算。
新增evaluate方法,用于重新计算值。计算后将dirty属性维护为false,当值未发生变动时不会重新计算。
那么更新处的代码则需要判断,通过lazy去判断是否为计算属性watcher,若为真则将dirty维护为true。这里不会执行渲染,当计算属性更新时,更新栈里会有两个watcher,一个是计算属性watcher,另一个是渲染watcher。而且渲染watcher会在计算属性之后执行(观察代码顺序可以看出)。
下边是部分watcher.js代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37
| constructor(vm,fn,options){ this.id = id++;
this.renderWatcher = options; this.getter = fn;
this.deps = []; this.depsId = new Set();
this.lazy = options.lazy; this.dirty = this.lazy; this.vm = vm;
this.lazy ? undefined : this.get(); }
depend(){ let i = this.deps.length; while(i--){ this.deps[i].depend(); } }
evaluate(){ this.value = this.get(); this.dirty = false; }
update(){ if(this.lazy){ this.dirty = true }else{ queueWatcher(this); } }
|
depend()是让dep记住watcher,下次执行渲染时才能将计算属性一并渲染。
Watch
简介
watch就是一个更加直白的观察者模式了:当某个值发生变化,则执行回调。同理是存在一个对于watch的watcher。
那么watch的形式在vue2中主要有:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| watch:{ firstname(newValue,oldValue){ console.log(newVlaue,oldValue); }, firstname:[ (newValue,oldValue)=>{ console.log(newVlaue,oldValue); }, (newValue,oldValue)=>{ console.log(newVlaue,oldValue); }, ], firstname: 'fn', }
vm.$watch(()=>vm.firstname,(newVal,oldVal)=>{ console.log(newVlaue,oldValue); })
|
在底层不论哪种形式,最终都会转成$watch()。
初始化
与计算属性同理,需要先获取到所有的watch选项,再循环处理。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| if(opts.watch){ initWatch(vm); }
function initWatch(vm){ let watch = vm.$options.watch;
for(let key in watch){ const handler = watch[key];
if(Array.isArray(handler)){ for(let i=0;i<handler.length;i++){ createWatcher(vm,key,handler[i]); } }else{ createWatcher(vm,key,handler); } } }
|
处理watch
watch处理比较简单,因为在底层都是调用$watch钩子,那么只要处理好形式,再统一调用这个钩子即可。
1 2 3 4 5 6
| function createWatcher(vm,key,handler){ if(typeof handler === 'string'){ handler = vm[handler] } return vm.$watch(key,handler); }
|
$watch实现与watcher调整
watch只需要监听到变化再执行一个回调即可,同理要建一个专门处理watch的watcher,options传入user:true进行标识。
1 2 3 4
| Vue.prototype.$watch = function(expOrfn,cb,options={}){ new Watcher(this,expOrfn,{user:true},cb) }
|
剩下的处理都在watcher中。
在watcher.js里,我们在构造函数需要额外新增一个cb用于接受回调函数。在构造函数内部接收user和cb。
在run函数中判断user,为true则执行cb()回调。
watch的老值和新值如何处理?
在构造函数中取出一次get值,此时是新建watcher时执行的一次,作为初始值。当后续数值变化更新时,会由Dep触发更新再次执行get。我们分别取到两次get返回值,作为新值和老值。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35
| constructor(vm,exprOrFn,options,cb){ this.id = id++;
this.renderWatcher = options;
if(typeof exprOrFn === 'string'){ this.getter = function(){ return vm[exprOrFn]; } }else{ this.getter = exprOrFn; }
this.deps = []; this.depsId = new Set();
this.lazy = options.lazy; this.dirty = this.lazy; this.vm = vm;
this.cb = cb; this.user = options.user;
this.value = this.lazy ? undefined : this.get(); }
run(){ let oldValue = this.value; let newValue = this.get(); console.log('this',this); if(this.user){ this.cb.call(this.vm,oldValue,newValue); } }
|
至此,已能实现watch的基本功能。