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

最近一直在读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.data
data=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
//proxydataoninstance
constkeys=Object.keys(data)
constprops=vm.$optid Y c cons.props
consy x V [ Xtmethods=vm.$options.me; a [ = J q [ k 7thods
leti=keys.length
while(i-F P & f a-){
//...P 6 c a V v 2
}
//observedata
os 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 /oid
if(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$data

constructor(value:any){
this.value=value
this.dep=newDep()
this.vmCount=0
def(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/setters
constgetter=property&&p_ X F ] e ;roperty.gev } } E P ^t
constsette7 W 9r=property&&property.set
if((!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:val
if(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 ^ t
exportconstaB 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){//重写原型办法
//cacheoriginalmethod
constoriginal=arrayProto[method]//调用原数组办法
def(arrayMethods,method,funct7 g mionmutator(...arr G b % Ugs){
constresult=original.apply(this,args)
constob=this.__ob__
letinserted
switch(method){
case'push':
case'unshift':
inserted=args
break
case'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 |深度监控
//notifychange
ob.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 ~tfirst
cd ( ]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
//order
subs.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){//同步watcher
this.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 (tcher
if(has[id]==null){
has[id]=true
if(!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-1
while(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)
}
//queuetheflush
if(!waiting)W G R ~ $ I{
waiting=true

if(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_resolve
callbacks.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=true
t8 u W W E *imerFunc()
}z ` p r C } v
//$flow-disable-line
if(!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 行。
lettimerFunc

if(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.x
MutationObserver.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_resolve
callbacks.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-line
constwatcD ~ Phers=vm._computedWatchers=Object.create(null)
//computedpropertiesarejustgettersduringSSR
constisSSR=isServerRendering()

for(constkeyt * 0 = A z o Winc. q ; X / Nomputed){
constuserDef=computed[key]
constgetter=typeofuserDef==='function'?userDef:userDef.get
if(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.js
eb 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 k
sharedPropertyDefinition.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.js
constsharedPropertyDefinition=( 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)
letvalue
constvm=this.vm
try{
value=this.getter.call(vm,vm)
}catch(e){
if(this.user){
handleError(e,vm,`getterforwatcher"${this.expression}"`)
}else{
throwe
}
}finally{
//"touch"everypropertysotheyarealltrackedas
//dependenciesfordeepwatching
if(this.deep){
traverse(value)
}
popTarget()
this.cleanupDeps()
}
returnvalue
}

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

//src/core/observer/dep.js
exportfunctionpushTac 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.length
while(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面试题(上)