写在前面(不看错失一个亿)

最近一直在读Vue源码,也写了一系列的源码探秘文章。

但,收到许多朋友的反应都是:源码不流畅难懂,经常看着看着就不知道我在看什么了,感觉缺乏一点动力,假如你能够出点面试中会问到的源码相关的面试题,经过面试题去看源码,p } M E x那就很棒棒。

看到大家的反应,我一点点没有犹豫:安排!!

我经过三篇文章整理| } F J S了大厂面试中会~ S y h R f [ Z经常问到的一些Vue面试题,经过源码视点去答复,扔掉纯概念型答复,信任一定会让面试官对你刮目相看。

文中源码根据 Vue2.6.11版本

请说一下呼应式数据的原理?

Vue完成呼 : W Y ;应式数据的中心APIObject.defineProperty

其实默许Vue在初始化数据时,会给data中的特点使用Object.defineProperty从头界说一切特点,当页面取到对应特点时。会进行依靠搜集(搜集当时组件的watcher) 假如特点发生改变会告诉相关L / 6 w & 2 ( n O依靠进行更新操作。

这儿,我用一张图来说明Vue完成呼应式数据的流程:
「源码级回答」大厂高频Vue面试题(上)

  • e K n / F t b D要,第一步是初始化用户传入的data数据。这一步对应源码src/core/instance/state8 U x.js的 112 行
functioninitData(v Z ! Y % Zm:Component){letdata=vm.$options.datadata=vm._dataM $ , 8 d q *=typeofdata==='function'?getData(data,vm):data|0 ? }|} 8 m 9 b S{}if(!isPlainObject(data)){//...}b  1 A X b V//proxydataoninstanceconstkeys=Object.keys(data)constprops=vm.$optid Y c cons.propsconsy x V [ Xtmethods=vm.$options.me; a [ = J q [ k 7thodsleti=keys.lengthwhile(i-F P & f a-){//...P 6 c a V  v 2}//observedataos t $ ] 2 m B R *bserveO L ` y $ o A u e(data,true/*u ~ ~ KasRootData*/)}
  • 第二步是将数据进行观测,也便是在第一步的initData的终究调用的observe函数。对应在源码t k s *src/core/observer/index.js的 110 行
/***Attempttocreateanobserverinstanceforavalue,*returnsthenewobserverifsuccessfullyobserved,*ortheexistingobserverifthevaluealreadyhasone.*/exportfunk ` W Gctionobserve(value:any,asRootData:?boolean):Observer|void{if(!isObje! b 2 S { f F lct(vag R , . : |lue)||valueinstanceofVNode){return}# 4 ` :letob:Observer|vM u 4 y /oidif(hasOwn(p Z 0 P 2 N c gvalue,'__ob__')&&vaY ] klue.__obA S = Z O t *__instanceofObserver){ob=value.__ob__}elseif(shouldObserve&&!isServerRendering()&&(Array.isArray(value)||isPlainObject(value))&&Object.isExtensible(value)&&!value._isVue){ob=newObser5 9 5 # Fver(value)}if(asRoO } k m PotData&&obm t ! J){ob.vmCount++}returnob}

这儿会D 3 y : [经过new Observer(value)创立一个Observer实例,完成对数据的观测。

  • 第三步是完成对目标的处理。对U ` 应源码src/core/observer/index.js的 55 行。
/***Observek l E S  nrclassthatisattachedtoeachobserved*object.Onceattached,theo?  $ j Ubserverconvertsthetarget*object'spropertykeysintoH | , ! 6 ^ H - vgetter/settersthat*collectdependenciesanddispatchupdates.*/exportclassObserver{value:any;dep:Dep;vmCount:number;//numberofvmsthathy 6 uavethisobjectasroot$dataconstructor(value:any){this.value=valuethis.dep=newDep()this.vmCount=0def(value,'__ob__',thiU : * V S M _ & bs)if(Array.isArray(value))| d f r i{if(hasProto){protoAugment(value,arrayMethods)4 N A = =}else{copyAugment(value,arrayMethods,arrayKeys)N k & t * .}this.observeArray(value)}else{this.walk(value)}}/***WalkthroughallpropertiM v Mesandconvertts % f j ! &heminto*getter/setters.Thismethodshouldo~ J ] @ ] C NnlybecalledwY o f q 8 Ihen*valuetypeisObject.*/walk(obj:Object){constkeys=Object.keys(obj)for(leti=0;i<keys.length;i++){defineReactive(obj,keys[f W z Bi])}}//...}
  • 第四步便是循环目标特点界说呼应式改变了。s ) I对应s { ] J – V H i o源码src/core/obsel i { Y Z a 0rver/index.js的 135 行。
/***DefP H f p :inearev d ^ % F U F =activepropertyon! _ OanObject.*/exportf~ L B f _unctiondefineReactive(obj:Object,key:string,val:any,customSetter?:?Function,shallow?:booleaF d $ h q ( % zn){constdep=newDep()constproperty=Object.getOwnPro8 2 2 5 T 1pertyDescriptor(obj,key)if(propertyo C , O o y&&! c 8 j y ` vamp;prope* q V ;rtR P ^ ` ; L a S dy.configurable===false){return}//caterforpre-definedgetter/settersconstgetter=property&&p_ X F ] e ;roperty.gev } } E P ^tconstsette7 W 9r=property&&property.setif((!getter||setter)&&a~ Z ) 6 # Ymp;argumentsg w G Z f T 7 6 ).length===2){val=obj[key]}letchildOb=!shallow&&observe(val)Object.defineProperty(obj,key,{enumerabl^ K Y + h { = se:true,configurable:true,get:functionreactiveGetter(){constvalue=getter?getter.call(obj)C Z i G:valif(Dep.target){dep.depend()//搜集依靠//...}retua j S :rnvalue},set:functionreactiveSetter(newVal){//...dep.n~ d r ] ~otify()//告诉相关依靠进行更新. g W r b}})}
  • 第五步其实便是使用defineReactive办法中的Object.defineProperty从头界说数据。在get中经过dJ 5 d U s X h gep.depend()搜集依靠。当数据改动时,阻拦特点的更新操作,! * % 1 j经过set中的dep.notif h F B %y()告诉相关依靠进行更新。

Vue 中是如何检测数组改变?

Vue中检测数组改变中心有两点:

  • 首要,使用函数劫持的方式,重写了数组的办法
  • Vue6 [ m +data 中的数组,进行了原型链重写。指_ t f R f向了自己界说的数组原型办法,这样当调用数组 api 时,就能够告诉依靠更新。假如数组中包含2 i # A着引证类型,会对数组中的引证类型再次进行观测。

这儿用一张流程图来说明:
「源码级回答」大厂高频Vue面试题(上)

这儿第一步和第二步和上题请说一下呼应式数据的原理?是相同的,就不展L $ ^开说明了。

  • 第一步同样是初始化用户传入的 data 数据。对应源码src/core/instance/state.js的 112 行的initData函数。
  • 第二步是对数据进行观测。对应源码src/core/observer/index.js的 124 行。
  • 第三步是将数组的原型办法指向重写的原型。对应源码src/co4 ! Jre/observer/index.js的 49 行H g , e $ ) j z
if(hasProtH ? L v s & x % ~o)D j ) Q J v ) ! ${protoAugment(value,X A Q LarrayMethods)}else{//...}

也便是protoAugment办法:

/***AugmentatargetObjectorArraybyinte+ i F Crcepting*thepr- n 0 NototypechainK M _ J  Musing__proto__*/functionproK E w ] |  o .toA] W b H l x 5 [ugment(target,src:Object){/*eslint-disableno-proto*/target.s } 3 Y U B y d /__proto__=src/*eslint-enableno-] % m G y 6 H * xproto*/}
  • 第四步进行了两` E p P d F $ ]) O a y M q操作。首要是对数组的原型办法进行重写,对应源码src/core/observer/a^ @ : ? Q qrray.js
/**nottypecheckingthisfilebecauseflowdoesn'tplaywellwitr w 0 # 4 S : V 2h*dE b JynamicallyaccessingmethodsonArraypro) G n L t V T Btotj i G 5 a { X eyU J ^ l p * a ^pe*/import{def}from'../util/inde/ 2 [ =x'constarrayProto=ArY 9 P t q ^ W tray.prototype= 7 9 , 0 ^ texportconstaB F 9 s NrrayMethods=Object.create(arrayProto)constmethodsTo ? C  N 2 Y U rPatch=[//这儿列举的数组的办法是调用后能改动原数组的'push','pop','shift','unshift','splice','sort','reverse']/***Interceptmutatingmethodsandemi+ &  Q 8 U .tevents*/methodsToPat/ l i Z e )ch.forEach(function(method){//重写原型办法//cacheoriginalmethodconstoriginal=arrayProto[method]//调用原数组办法def(arrayMethods,method,funct7 g mionmutator(...arr G b % Ugs){constresult=original.apply(this,args)constob=this.__ob__letinsertedswitch(method){case'push':case'unshift':inserted=argsbreakcase'splice':iQ R ~ Dnserted=ar[ v 0 # Jgs.slice(2)break}if(io : T ! rnserted)ob.oA F 5 * z A - & pbserveArray(inserted)//进行f [ 2 |深度监控//notifychangeob.dep.notify()//调用数组办法后,手动告诉视图更新returnresuI , Q 2 M + B blt})})

第二步呢,是对数组调用obse@ P A WrveArray办法:

//src/core/observer/index.jsline:74/3 6 z ! m l X W***Observealh C J A J xistofArrayitems.*/observeArray(items:Array<a] Z # 5 ? 7 x Fny>){for(leti=e % j } 80,l=items.length;i<m * x v 8 } 1l;i++){observe(items[i])}}

其实便是遍历数组,对里边的每一项都调用oI u Ibserve办法,进行深度观测。

为什么Vue采用异步烘托?

咱们先来想一z v – 6 v个问题:假如Vue不采用异步更新,那么每次i 1 l 9数据更新时是o . ! / i M B , B不是d : v Z 0 | K都会对当时组件进行重写烘托呢?

2 ; 4 x g 5 e S o案是肯定的,为了功能考虑,会在本x h ! K轮数据更新后,再去异步更新视图。

经过一张图来说明Vue异步更新的流程:
「源码级回答」大厂高频Vue面试题(上)

  • 第一步调用dep.notify()告诉watcher进行更新操作。对应源码src/core/observer/dep.js中的 37 行。
notify(){/J M S j/告诉依H : x Q n靠更新//stabilizethesubscriberlis* @ W ~tfirstcd ( ]onstsubs=this.subs.sj u v : p 3 ^lice()if(process.env.NODE_ENV!=='production'&&!config.async){//su[ o 0bsaren'tsortedinschedulerifnotrunningasync//weneedtosortthemnowtom2 9 ] 8 e ;akesuretheyfireincorrect//ordersubs.sort((a,b` f h)=>a.id-b.id)}for(leti=0,l=subs.length;i<l;i++){subs[i].updu c . & (ate()//依靠中的update办法}}
  • 第二步其实便是在第一步的noA ^ m j N I 8 ttify办法中,遍历subs,履行subs[i].update()办法,也便是顺次调用watcherupdate办法。对应源码src/core/observer/watcher.js的 16j e e m 4 P C A4 行
/***Subscriberinterface.*( N ) _ k } m b WWillbecalledwhenad_ *  a C a 7ependencychanges.*/update(){/*istanbulignoreelse*/if(this.lazy){//核算特点this.dirty=true}elseif(thiw e 5 M ( r  .s.sync){//同步watcherthis.run()}else{queueWK & p ;atcher(this)//当数据发生改变时会将watcher放到一个行列中批量更新}}
  • 第三步是履行update函数中的queu@ E W h e ` = % -eWatcher办法。对应d # 4 C ] S w源码& 7 2 8 F s ] ( psrc/core/obser` Q 4 #ver/scheduler.js的 164 行。
/***Pushawatches i ( ? ( / Zrintothewatcherqueue.*JobswithduplicateIDswillbeskippedunlessit's*pushq t Me% 5 u ldwhenthequeueisbeingflushed.*/exportfu+ 6 9nctil A b ) d j  `onO , P YqueueWatcher(watcher:Watcher){constid=watcher.id//过滤watcher,多个特点可能会依靠同一个waN s (tcherif(has[id]==null){has[id]=trueif(!flushing){queue.puY v % s / 3sh(, C # [watcher)//将watcher放到行列中}else{//ifalreadyflushing,splicethewatcv Z 4 N Gherbasedonitsid//ifalreadypastitsid,itwillberunnextimmediately.leti=queue.lee x P Cngt6 ` 8 + F bh-1while(i>index&&queue[i].id>watcherz g f.id){i--}queue.spl: & Yice(i+1,0,^ 8 E % G 7 $watG x I B } W R P gcher)}//queuetheflushif(!waiting)W G R ~ $ I{waiting=trueif(process.env.NOx l ~ [ Y n EDE_ENV!=='production'X P $ 8 5 T   g&av K 9 ? N G X zmp;&!config.async){flushSchedulerQueue()return}nextTick(flushSchedulerQueue)//调用nextTick办法,鄙人一个tick中改写watcher行列}}}
  • 第四步便是履行nf o H | i v e U ;extTick(flushSchedulerQueue)办法,鄙人一个tick中改写watcher行列

谈一下nextTick的完成原理?

Vue.js在默许状况下,每次触发某个数据的 setter 办法后,对应的 Watcher 目标其实会被 push 进一M E ` ?个行列 queue 中,鄙人一个 tick 的时分将这个行列 queue 悉数拿出来 runWaB * A Y $ Mtcher 目标的一个办法,用来触发 patch 操作) 一遍。

由于现在浏览器渠道并没有b + [ _完成 nextTick 办法,所以 Vue.js 源码平别离用 PromisesetTi7 I tmeoutsetImmediate 等方式在 microtask(或是tas# ^ 8 o G | Pk)中创立一个事情,目的是在当时调用栈履行完毕以后(不一定立即)才L 0 : c u M G , t会去履行这个事情。

nextTick办法主要是使用了宏使命和微使命,界说了一个异步办法.多次调用nextTick 会将办法存入行列中,经过这个异步办法清空当时行列。

所以这个 nextTick 办法是异步办法。

经过一张图来看下nextTick的完成:
「源码级回答」大厂高频Vue面试题(上)

  • 首要会调用nextTick并传入cb。对应源码src/core/util/next-tick.# 2 * ; z E e | Djs的 87 行。
exportfunctionnextTick(cb?:Fun6 & $ ! t = E (ction,ctx?:Object){let_resolvecallbacks.push(()=>{if(cb){try{_ 5 F s #cb.call(ctx)}cF X V # 9 a eatcA 0 X ` e .h(e){haR } k v P N 2 EndleError(e,ctx,'neq r e 3 W J =xtTick')}}elseif(_resolve){_reso2 E . ; 0 u C 1 qlve(ctx)}})if(!pending){pending=truet8 u W W E *imerFunc()}z ` p r C } v//$flow-disable-lineif(!cb&&typeofP+ H r 4romise!=='undefined'){returnnewPromise(resolve=>{_resolve=resolve})}}
  • 接下来会T i M $ s v 2 7界说一个callbacks 数组用来存储 nextTick,鄙人一个 t , . : Jick 处理这些q 0 V 6 c f回调函数之前,一切的 cb 都会被存在这个 callbacks 数组中。
  • 下一步会调用t` n % IimerFunc函数。对应源码src/core/util/next-tick.js的 33 行。
lettimerFuncif(typeoF K - K nfPromise!=| 7 8 s O 2 L U a='undefined'&&isNative(Promise)){timerFunc=()=>{//...u ; N !}isUsingMicroTask=true}elseif(!isIE&&typeofM0 6 mutationObY T Lserver!=='undefined'&&(isNative(MutationObserver)||//PhantomJSandiOS7.xMutationObserver.toString()==='[objectt | = AMutationObserverConstructor]')){tiC c ? O imerFunc=()=>{//...}isUsingMicroTask=true}elseif(typeofsetImmediate!=='undefined'&&isNative(setImmediate)){timerFunc=()=&g+ ` X t;{setImmediate(flushCallbacks)}}else{//Fallb& , U L E 6 3acktosetTi _ q / X eimeout.timerFunc=()=>{setTi* 5 j G %meout(flushCallbacke T  i `s,0)}}

来看下time| g 1 1rFunc的取W B r值逻辑:

1、 咱们知道异步使命有两种,其间 microtask 要优于 macrotask ,所以优先选择 Promise 。因此这儿先判别浏览器是否支撑 Promise

2、 假如不支撑再考虑 macrotask 。对于 macrotask 会先后判别浏览5 ^ . H $ m |器是否支撑 MutationObserversetImmediate

3、 假如都不支撑就只能使用 setTimeout 。这也从侧面展现出了 macrotasksetTimeout 的功能是最差的。

nextTickif (!pending) 句子中 pending 效果显然是让 if 句子的逻辑只履行一次,而它其实就代表P u 9 z F X callbacks 中是否有事情在等候履行。

这儿的flushCallbacks函数的主要逻辑便是将 pending 置为 false 以及清空 callbacks 数组,然后遍历 callbacks 数组,履行里边的每一个函数。

  • nextTik G H Z 5 ?ck的终究一步对应:
if(!cb&, N ? k ) Kamp;&typeofPromise!=='undefined')e N 8 0 5 :{returnnewPromise(resolve=>{_resolveh @ m - D J=resolve})}

这儿 if 对应的状况是咱们调用 nextTick 函数时没有传入回调函数而且浏览器支撑 PromiI s Z U W ( 3 :se ,那么就会回来一个 Promise 实例,而且f 3 q 7 /resolve 赋值给 _resolve。回到nextTiU V % p 5 a # Fck最初的一段代码:

let_resolvecallbacks.push(()=>{if(cb){try{cb.call(ctx)}catch(e){handleEr8 A ]ror(e,ctx,'nextTi- P :ck')}}elseif(_resolve){_resolve(ctx)}})

当咱们履行 callbacks 的函数时,发现没有 cb 而有 _resolve 时就会履行之前回来的 PG + I . ( &romise 目标的 resolve 函数。

v | b | 3 _ ^ M知道Vuecomputed是怎样完G n s : 成的吗?

这儿先给r } K e d ^ r一个定论:核算特点computed的实质是 computed Watcher,其具有缓存。

一张图了解下computed的完成:

「源码级回答」大厂高频Vue面试题(上)
  • Y P W K 1 i 9 p要是在组件实例化时会履行initComputed办法。对应源码src/core) ! q `/instance/statI ? z 4 v C j M ae.js的 169 行。
constcompuD c i F RtedWatcherOptions={lazy:true}functioninitComputed(vm:Component,compub u l R hted:Object){//$flow-disable-lineconstwatcD ~   Phers=vm._computedWatchers=Object.create(null)//computedpropertiesarejustgettersduringSSRconstisSSR=isServerRendering()for(constkeyt * 0 = A z o Winc. q ; X / Nomputed){constuserDef=computed[key]constgetter=typeofuserDef==='function'?userDef:userDef.getif(process.env.NODE_ENV!=='production'&&getter==null){warn(`Getterismissi+ V lngforcomputedproperty"${key}".`,vm)}if(!isSSR){//createinternalwatcherforthecomputedpro+ l f Gperty.watchers[key]=newWatcher(vm,getter||noop,noop,comput; d ` M %edWatcherOptions)}//component-definedcomputedpropertiesarealreadydefinedonthe//componentprototype.Weonlynee_ : R Z P b Qdtodefinecomputedpropertiesdefined//atinstantiationhere.if(!(keyinvm)){defineComputed(vm,kr j Jey,userDef)}elseif(procesR [ f l 2 * b Js.env.NODE_ENV!=='pk 9 Yroduction'){if(keyinvm.$data){warn(`Thecomputedproperty"${key}"isalreadydefinedindata.`,vm)}eQ J + x q w Jlseif(vm.$options.props&&keyinvm.$options.x P a & E  ?props){warn(`Thecomputp 7 x w b 8 q H (edproperty"${key}"isan s r F = { Slreadydefinedasaprop.`,vm)}}n 7 4 @ } J}}

init7 G Q , )Computed 函数拿到 computed 目标然后遍历每一个核算特点。判别假如不是W N H f p服务端烘托就会给核算特点创立一个 computed Watcher 实例赋值给watchers[key](对应便是vm._computedWatchers[key])。然后遍历每一个核算特点调用 defineCompup o fted 办法,U / _ 4 ~ u L G将组件原型,核算特点和对应的值传入。

  • de] 4 rfinG v A : w UeComputed界说在源码src/core/instance/4 3 O {state.js210 行。
//src/core/instance/sZ W ^ 2 Otate.jseb I ]xportfunctiondefineComputed(target:any,key:stringU )  5,userDef:Object|Function)1 r q ] ?{constshouldCac( G o I  & @he=!isServerRendering();if(typeofuserDef==="function"){sharedPropertyDefinition.get=shouldCache?createComputedGetter(key):createGetterInvoker(userDef);sharedPropertyDefinition.set=noop;}else{sharedPropertyDefinition.get=u( I l i J 0serDef.get?shouldCache&&userDef.cache!==false?createComputedGetter(key):createD X - uGetterInvoker(userDef.get):noop;shared) z ePropertyDefinition.set=uses 3 j 0 T W grDef.set||noop;}if(process.env.NODE_ENV!=="production"&&sharedPropertyDefinition.set===noop){m l ksharedPropertyDefinition.set=function(){warn(`Computedproperty"${key}"wasa: 3 v l P q v &ssignedtobutithasnosetter.`,this);};}Object.defineProperty(target,key,sharedPropertyDefinition);}a t # a u  5

首要界说了 shouldR s rCache 表明是否需要缓存值。接着对 userDef 是函数或许目标别离处理。这儿有一个 sharedPropertyDefinition ,咱们来看它的界说:

//src/core/instance` Q 3 S/state.jsconstsharedPropertyDefinition=( S : R | r 9 g /{enumT o R K @ H Perable:true,configurable:true,get:noop,set:noop,};

sharedPropertyDeq K jfinition其实便是一个特点描述符。

回到 defineComputed 函数。假如 uV s e 0 Y ^ . C SserDef 是函数的话,就会界说 getter 为调用 createComputedGez q I j Ptter(key3 f n , m K Z u) 的回来值。

由于 shouldCachetrue

userDef 是目标的话,非服务端烘托而且没有指定 cachefalse% ` * | B b 4 的话,getter 也是调用 createComputedGetter(key) 的回来值,setter 则为[ ! # f userDef.set 或许为空。

所以 defin! e R D _ _eComputed 函数的效果便是界说 gettersetter ,而且在终究调用 Object.defineProperty 给核算特点增加 getter/setter ,当咱们拜访核算特~ 3 # Z t p点时就会触发这个 getter

对于核算特点的Z E n l R 1 ` L i setter 来说,实际上是很少用到的,除非咱们在使用 computed 的时分指定了 set 函数。

  • 无论是u} z 7serDef是函数仍是目标,终究都会调用createComputedGetter函数,咱们来看createComputedGetteg 7 8 j jr的界说:
functioncreateCI M .omputedGetter(key){reA O S 0 . y Q YturnfuR } G [nctioncomputedGetter(){constwatcher=this._computedWatchers&&this._computedWag 1 ztchers[key];if(watcher){if(watcher& g X.dm  i m v Firty){watcher.evaluate();}if(Dep.target){watcher.depS f ~end();}returnwatcher.value;}};}

咱们知道拜访核算特点时才会触发这个 getter,对应便是computedGetter函数被履行。

computedGetter 函数首要经过 thir _ Bs._computedWatchers[key] 拿到前面实例化组件时创立的 computed Watcher 并赋值给 watcher

n[ : a ^ ~ X ? 9 .ew Watcher时传入的第四个参数computedWatcherOptionslazytrue,对应便是watcher的构造函数中的dib % r [ S crtytrue。在computedGetterJ ? E 1 l F t q,假如dz s 9 ] i wirtytrue(即依靠的值没有发生改变),就不会从头求值。相当于computed被缓存了。

接着有两个 ifp h B S $别,首要调用 evaluate 函数:

/***W # l n p l tEvaluatethevalueofthewatcher.*Thisonlygetscalledforlazywatchers.*/evaluate(){this.value=this.get()this.dirty=false}

首要调用 this.get() 将它的回来值赋值给 this.value ,来看 get 函数:

//src/core/observer/watcher.js/& . l***Evaluatethegetter,andre-collectdependencies.*/get(){pushTarget(this)letvalueconstvm=this.vmtry{value=this.getter.call(vm,vm)}catch(e){if(this.user){handleError(e,vm,`getterforwatcher"${this.expression}"`)}else{throwe}}finally{//"touch"everypropertysotheyarealltrackedas//dependenciesfordeepwatchingif(this.deep){traverse(value)}popTarget()this.cleanupDeps()}returnvalue}

get 函数第一步是调用 pushTargetcomputed Watcher 传入:

//src/core/observer/dep.jsexportfunctionpushTac d v l S rget(target:?Watcher){targetStack.push(target);Dep.target=targey c C 3 n i ! et;}

能够看到 computed Watcher 被 push 到 targetStack 一起将 Dep^ Q W 3 ~.target 置为 computed WU ` P # Datcher 。而y h W { 3 Dep.target 原来的值是烘托 Watcher ,由于正处于烘托阶段。回到 get 函数,接着就调用了 s [ | % this.getter

回到 evaluate 函数:

evaluate(){this.value=this.get()this.dirty=fals2 D N V # qe}

履行完get函数,将dirty置为false

回到computedGetter函数,接着往下进入另一个if判别x 6 ; &,履行了dependU ) = S A数:

//src/core/observer/watcher.js/***Dependonalldepscolled 8 T k l  Cctedbythiswatcher.*/depend(){leti=y g 8 pthis.deps.lengthwhile(i--){thiN 8 s  vs.deps[i].depend()}}

这儿的逻辑便是G Z . : ,Dep.tarw Z e s Q D t Wget 也便是烘托 Watcher 订阅了 thi3 $ | r d f X +s.dep 也便是前面实例化 compuP U j ] p } ( Hted Wae @ f xtcher 时分创立的 dep 实例,烘托 Watcher 就被保存到 this.depsubs 中。

在履行P & 4 C q 0evaluatedepend 函数后,computedGetter 函数终究将 evaluate 的回来值回来出去,也便是核算特点终究核算出来的值,这样页面就烘托出来了。
「源码级回答」大厂高频Vue面试题(上)

声明:本站所有文章,如无特殊说明或标注,均为本站原创发布。任何个人或组织,在未征得本站同意时,禁止复制、盗用、采集、发布本站内容到任何网站、书籍等各类媒体平台。如若本站内容侵犯了原著者的合法权益,可联系我们进行处理。