写在前面(不看错失一个亿)
最近一直在读Vue
源码,也写了一系列的源码探秘
文章。
但,收到许多朋友的反应都是:源码不流畅难懂,经常看着看着就不知道我在看什么了,感觉缺乏一点动力,假如你能够出点面试中会问到的源码
相关的面试题,经过面试题去看源码,p } M E x那就很棒棒。
看到大家的反应,我一点点没有犹豫:安排!!
我经过三篇文章整理| } F J S了大厂面试中会~ S y h R f [ Z经常问到的一些Vue
面试题,经过源码视点去答复,扔掉纯概念型答复,信任一定会让面试官对你刮目相看。
❝
文中源码根据 Vue
2.6.11
版本❞
请说一下呼应式数据的原理?
Vue
完成呼 : W Y ;应式数据的中心API
是Object.defineProperty
。
其实默许Vue
在初始化数据时,会给data
中的特点使用Object.defineProperty
从头界说一切特点,当页面取到对应特点时。会进行依靠搜集(搜集当时组件的watcher
) 假如特点发生改变会告诉相关L / 6 w & 2 ( n O依靠进行更新操作。
-
首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着引证类型,会对数组中的引证类型再次进行观测。
❝
这儿第一步和第二步和上题
请说一下呼应式数据的原理?
是相同的,就不展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轮数据更新后,再去异步更新视图。
-
第一步调用 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()
办法,也便是顺次调用watcher
的update
办法。对应源码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
悉数拿出来 run
( WaB * A Y $ Mtcher
目标的一个办法,用来触发 patch
操作) 一遍。
由于现在浏览器渠道并没有b + [ _完成 nextTick
办法,所以 Vue.js
源码平别离用 Promise
、setTi7 I tmeout
、setImmediate
等方式在 microtask
(或是tas# ^ 8 o G | Pk
)中创立一个事情,目的是在当时调用栈履行完毕以后(不一定立即)才L 0 : c u M G , t会去履行这个事情。
nextTick
办法主要是使用了宏使命和微使命,界说了一个异步办法.多次调用nextTick
会将办法存入行列中,经过这个异步办法清空当时行列。
❝
所以这个
nextTick
办法是异步办法。❞
-
首要会调用 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 |器是否支撑 MutationObserver
和 setImmediate
。
3、 假如都不支撑就只能使用 setTimeout
。这也从侧面展现出了 macrotask
中 setTimeout
的功能是最差的。
❝
nextTick
中if (!pending)
句子中pending
效果显然是让if
句子的逻辑只履行一次,而它其实就代表P u 9 z F Xcallbacks
中是否有事情在等候履行。❞
这儿的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知道Vue
中computed
是怎样完G n s : 成的吗?
这儿先给r } K e d ^ r一个定论:核算特点computed
的实质是 computed Watcher
,其具有缓存。
一张图了解下computed
的完成:
-
首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.js
210 行。
//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)
的回来值。
❝
由于
shouldCache
是true
❞
而 userDef
是目标的话,非服务端烘托而且没有指定 cache
为 false% ` * | B b 4
的话,getter
也是调用 createComputedGetter(key)
的回来值,setter
则为[ ! # f userDef.set
或许为空。
所以 defin! e R D _ _eComputed
函数的效果便是界说 getter
和 setter
,而且在终究调用 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
时传入的第四个参数computedWatcherOptions
的lazy
为true
,对应便是watcher
的构造函数中的dib % r [ S crty
为true
。在computedGetter
中J ? E 1 l F t q,假如dz s 9 ] i wirty
为true
(即依靠的值没有发生改变),就不会从头求值。相当于computed
被缓存了。❞
接着有两个 if
判p 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
函数第一步是调用 pushTarget
将 computed 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 ; &,履行了depend
函U ) = 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.dep
的 subs
中。
在履行P & 4 C q 0完 evaluate
和 depend
函数后,computedGetter
函数终究将 evaluate
的回来值回来出去,也便是核算特点终究核算出来的值,这样页面就烘托出来了。