想要了解原理,还是得看源码。最近在网上找了很多vue的初始化方法(8个init恶魔。。。)因为也是一步步了解,initComputed计算属性的初始化方法有多少?到处都看的不是很清楚,网上也说的很模糊(想深入就一定要深入...),所以调试了好几天,总算有了一些端倪。既然写出来了,可以帮自己重新梳理一下思路,也可以让大佬们指出错误。先不讲基本的双向绑定原理。可以搜索相关教程,我们还是需要先看懂简单的例子才能进入正题。我们先看一下initComputed的源码结构。在此之前,先举个例子说明一下functioninitComputed(vm,computed){console.log('%cinitComputed','font-size:20px;border:1pxsolidblack')varwatchers=vm._computedWatchers=Object.创建(空);//计算属性只是SSR期间的gettervarisSSR=isServerRendering();for(varkeyincomputed){varuserDef=computed[key];vargetter=typeofuserDef==='函数'?userDef:userDef.get;if("development"!=='production'&&getter==null){warn(("Getterismissingforcomputedproperty\""+key+"\"."),vm);}//minxing---consoleconsole.log('%cinitComputeddefineswatchers[key]=newWatcher(vmgetternoopcomputedWatcherOptions)','color:white;padding:5px;background:black');if(!isSSR){//为计算属性创建内部观察器。/***熟悉的newWatcher,创建一个subscriber,为了后面收集依赖*例子中绑定num,lastNum和计算属性comNum*也就是说一个dep里面有两个dep,subs是*dep1.subs:[watcher(num),watcher(comNum)]*dep2.subs:[watcher(lastNum),watcher(comNum)]*dep3.......*请看前面的例子,页面html中没有渲染{{lastNum}};按理说lastNum的getter不会被执行*这样就不会和计算出来的属性关联起来了。如果lastNum变了,不会触发dep2的notify()释放*页面上自然看不到comNum变了,但是运行后却不是这样,为什么呢?*这样就引出了下面的initComputed方法---依赖收集(watcher.prototype.depend)!*当时我也是花了很长时间才知道这个depend方法的作用,以后再说。.lastNum;}}*这个getter什么时候执行,会在Watcher.prototype.evaluate()方法中执行*所以watcher中的evaluate()和depend()方法都和initComputed*/watchers[key]=newWatcher(vm,getter||noop,noop,computedWatcherOptions);}//组件定义的计算属性已经在//组件原型上定义。我们只需要在这里定义实例化时定义的计算属性。//判断后定义计算属性---(与vm数据关联)if(!(keyinvm)){defineComputed(vm,key,userDef);}else{if(keyinvm.$data){warn(("计算属性\""+key+"\"已在数据中定义。"),vm);}elseif(vm.$options.props&&keyinvm.$options.props){warn(("Thecomputedproperty\""+key+"\"isalreadydefinedasaprop."),vm);}}}}defineComputed方法这个方法比较简单,主要是将计算属性绑定到vm上。以下重要的createComputedGetter方法函数defineComputed(target,key,userDef){console.log('%cdefineComputed','font-size:20px;border:1pxsolidblack')varshouldCache=!isServerRendering();if(typeofuserDef==='function'){sharedPropertyDefinition.get=shouldCache?createComputedGetter(key):userDef;sharedPropertyDefinition.set=noop;}else{sharedPropertyDefinition.get=userDef.get?应该缓存&&userDef.cache!==false?createComputedGetter(key):userDef.get:noop;sharedPropertyDefinition.set=userDef.set?userDef.set:noop;}if("development"!=='production'&&sharedPropertyDefinition.set===noop){sharedPropertyDefinition.set=function(){warn(("Computedproperty\""+key+"\"wasassignedtobutit没有二传手。”),this);};}Object.defineProperty(target,key,sharedPropertyDefinition);}functioncreateComputedGetter(key){console.log('createComputedGetterkey',key);返回函数computedGetter(){varwatcher=this._computedWatchers&&this._computedWatchers[key];如果(watcher){如果(watcher.dirty){watcher.evaluate();}if(Dep.target){watcher.depend();}returnwatcher.value}}}createComputedGetter方法,主要做了两件事1找到计算出来的属性的值---使用上面提到的调用watcher.evalute()方法执行watcher中的getter2依赖集合绑定,这是最重要的一点,如前所述,如果与计算属性关联的数据属性(lastNum)没有呈现在HTML中,watcher.depend()方法将执行这个依赖集合绑定指定的角色dep.subs[watcher(计算属性),watcher(计算关联属性1),...],这样当你改变lastNum时,会触发相应的dep.notify()方法通知订阅者执行update。数据更新,如果watcher.depend()方法被注释掉,页面会渲染lastNum({{lastNum}}),那么watcher.evalute()会执行watcher.get将计算watcher推入中dep.target,在渲染lastNum和执行getter的时候,这个watcher会作为依赖加入,所以lastNum和computed属性也会关联到depfunctioncreateComputedGetter(key){console.log('createComputedGetterkey',key);返回函数computedGetter(){varwatcher=this._computedWatchers&&this._computedWatchers[key];if(watcher){if(watcher.dirty){console.log('createComputedGetterwatcherevaluate===========');//评估watcher.evaluate();}if(Dep.target){console.log('createComputedGetterwatcherdepend===========');//依赖集合观察者。依靠();}console.log('%ccreateComputedGetterwatcher.valueis','color:blue;font-size:40px',watcher.value);returnwatcher.value}}}为了更好的解释,截两张图(都是基于tophtml的配置)。图1注释掉watcher.depend()方法,此时deps中没有dep:id4。事实上,dep:id4在内存中已经定义好了,但还没有加入到deps中(因为没有依赖集合),而dep:id5和id6就是上面数组和递归数组中的元素。depdep:id3看清楚这是图组2依赖收集之后的deps了吗?综上所述,计算属性的基本原理是这样的。主要是根据我自己的理解。如有不妥请指出。哎,写代码还是舒服点,打描述太累了。.
