当前位置: 首页 > Web前端 > vue.js

vue-computed

时间:2023-04-01 00:14:39 vue.js

源码分析从问题入手,重点讲解几个问题的源码实现1.computed的旧身份来源2.computed是如何计算的3.computedcache是??如何工作的?4.计算什么?时间初始化5.computed如何直接使用instance访问的问题就不按顺序解决了,因为这些问题会相互关联。在探索源码的过程中,自然会得到答案初始化过程functionVue(){...其他处理initState(this)...解析模板,生成DOM插入页面}functioninitState(vm){varopts=vm.$options;如果(opts.computed){initComputed(vm,opts.computed);}.....}没错,调用Vue创建实例时,会处理各种选项,包括处理computed。处理computed的方法是initComputed,下面给出源码initComputedfunctioninitComputed(vm,computed){varwatchers=vm._computedWatchers=Object.create(null);for(varkeyincomputed){varuserDef=computed[key];vargetter=typeofuserDef==='函数'?userDef:userDef.get;//每个computed都创建一个watcher//watcher用于存储计算值和判断是否重新计算watchers[key]=newWatcher(vm,getter,{lazy:true});//判断是否存在同名属性if(!(keyinvm)){defineComputed(vm,key,userDef);}}}initComputed这段代码做了几件事1.每个计算都被分配了watcher2和defineComputedHandle3.收集所有计算的观察者。好吧,让我们一一说说这三件事。1.每个计算都分配了一个观察者。computed和watcher有什么区别?1.保存计算过的计算函数2.保存计算结果3.控制缓存的计算结果是否有效。看Watcher源码构造函数Watcher(vm,expOrFn,options){this.dirty=this.lazy=options.lazy;this.getter=expOrFn;this.value=this.lazy?未定义:this.get();};从这段源码来看,computednewWatcher(vm,getter,{lazy:true})传递了哪些参数所以,我们现在知道上面提到的三个技巧是怎么回事1.保存设置的getter将用户设置的computed-getter存放在watcher.getter中,用于后续计算2.watcher.value存放计算结果,但是这里有一个条件,因为偷懒的原因,不会创建新的实例,读取值马上到这里可以看作是对Vue的一种优化,只读computed,然后开始计算,而不是初始化开始计算值。虽然一开始没有计算,但是value的计算还是watcher.get的方法。我们看一下源码(省略部分代码,其他问题下面再说,会更详细展示)这个方法其实就是执行保存的getter函数获取计算值,很简单的Watcher。prototype.get=function(){//getter是watcher回调varvalue=this.getter.call(vm,vm);返回值};3.Computed创建新的watcher时,传入lazy是正确的。作用是缓存计算结果,而不是每次使用都重新计算。这里嘛,lazy也分到了dirty,为什么呢?因为lazy代表一个固定的描述,不能改变,表示watcher需要缓存,dirty表示缓存是否可用。如果为真,说明缓存脏了,需要重新计算。否则dirty默认为false,lazy赋值给dirty。就是给一个初始值,代表你控制缓存的任务开始了。所以记住,[dirty]才是真正控制缓存的关键,lazy只是起到一个开场的作用。具体如何控制缓存,下面会说2.defineComputed处理函数请参考源码defineComputed(target,key,userDef){//设置set为默认值,避免computed不设置setvarset=function(){}//如果用户设置了一个集合,则使用用户的集合if(userDef.set)set=userDef.setObject.defineProperty(target,key,{//包装get函数,主要用来判断计算缓存结果是否有效,所以可以直接访问2。set函数默认为空函数。如果用户设置,则使用用户设置3.CreateComputedGetter包装并返回get函数。重点在第三点,为什么重要?到这里两个问题都解决了,【月老撮合问题+缓存控制问题】马上提交createComputedGetter的源码functioncreateComputedGetter(key){returnfunction(){//获取对应key的computed-watchervarwatcher=this._computedWatchers[键];//如果计算出来的数据依赖变化,dirty会变为true,从而重新计算,然后更新缓存值watcher.valueif(watcher.dirty){watcher.evaluate();}//这里是月份老计算撮合的关键点是让双方建立关系.dirty){watcher.evaluate()}1.watcher.evaluate用于重新计算,更新缓存值,以及将dirty重置为false,表示缓存已更新。下面是源码Watcher.prototype.evaluate=function(){this.value=this.get();//执行update函数后,立即重置flagthis.dirty=false;};2、只有dirty为真时,才会执行evaluate,都说通过Controldirty来控制cache,但是怎么控制dirty呢?先说一个设定。计算出的数据A引用了数据数据B,即A依赖于B,所以B会收集A的watcher。当B发生变化时,会通知A更新,即调用A-watcher.update,见下源代码Watcher.prototype.update=function(){if(this.lazy)this.dirty=true;....还有其他无关操作,已略去};通知computed更新时,只需设置dirty为true,这样当comptued被读取时,会调用evalute重新计算。-D1和P指代C,C指代D2。理论上,当D改变时,C也会改变,C会通知P更新3.实际上,C允许D与P建立关系,这样当D发生变化时D可以直接通知P。没错,就是下面这段代码的鬼畜查看源代码需要几分钟才能绕过。怎么写都让人信服。我们先看一下watcher.depend的源码。Watcher.prototype.depend=function(){vari=this.deps.length;while(i--){//this.deps[i].depend();//dep是D的依赖收集器dep.addSub(Dep.target)}};这就是这一段的作用!(还是用上面的例子PCD代号来说明)让D的dependencycollector收集Dep.target,目前Dep.target是什么?没错,就是页面的watcher!所以这里D会收集page的watcher,所以累了会直接通知pagewatcher...你会问,为什么Dep.target是pagewatcher?您好,这里的内容有点多,有点复杂。坐下后死哥会给你一份源码礼物,收好。Watcher.prototype.get=function(){//更改Dep.targetpushTarget()//getter是watcher回调varvalue=this.getter.call(this.vm,this.vm);//恢复之前的watcherpopTarget()返回值};Dep.target=null;vartargetStack=[];functionpushTarget(_target){//缓存最后一个Dep.target用于以后恢复if(Dep.target){targetStack.push(Dep.target);}dep.target=_target;//将computedwatch赋值给Dep.target}functionpopTarget(){Dep.target=targetStack.pop();//还原为pagewatch}注意几句1.Pagewatcher.getter保存页面更新函数,computedwatcher.getter保存computedgetter2,watcher.get用于执行watcher.getter和设置Dep.target3,Dep.target会被缓存,下面开始月对接的详细过程1.页面更新,读取computed时,Dep.target会被设置为pagewatcher2,computed被读取,createComputedGetter包裹的函数被触发,会第一次进行计算,computed-watcher.evaluted被调用,然后computed-watcher.get被调用,调用Dep.target设置为computed-watcher,旧值pagewatcher被缓存。3.computed计算会读取数据。此时数据会被computed-watcher采集,computed-watcher也会保存到数据的dependencycollectordep中(为下一步做准备)。computed计算完成后,释放Dep.target,Dep.target恢复之前的watcher(pagewatcher)4.手动watcher.depend,让数据重新收集Dep.target,所以数据重新收集到恢复的pagewatcher和record附加一个数据变化后续流程总结一下,此时数据依赖collector=[computed-watcher,pagewatcher]数据变化,前向遍历通知,computed先更新,再更新page3.收集所有computedwatcher从源码中可以看到,为每一个computed创建一个新的watcher后,它们都会被收集到一个对象中挂在实例上。为什么要收集它们?我临时的想法是在createComputedGetter中获取到对应的watcher,其实可以通过watcher,但是这里的方法是传递key,然后通过key找到对应的watcher