一文帶你深入剖析vue3的響應(yīng)式_第1頁(yè)
一文帶你深入剖析vue3的響應(yīng)式_第2頁(yè)
一文帶你深入剖析vue3的響應(yīng)式_第3頁(yè)
一文帶你深入剖析vue3的響應(yīng)式_第4頁(yè)
一文帶你深入剖析vue3的響應(yīng)式_第5頁(yè)
已閱讀5頁(yè),還剩17頁(yè)未讀, 繼續(xù)免費(fèi)閱讀

下載本文檔

版權(quán)說(shuō)明:本文檔由用戶提供并上傳,收益歸屬內(nèi)容提供方,若內(nèi)容存在侵權(quán),請(qǐng)進(jìn)行舉報(bào)或認(rèn)領(lǐng)

文檔簡(jiǎn)介

第一文帶你深入剖析vue3的響應(yīng)式緩存功能,如果值沒(méi)有變化,就會(huì)返回上一次的執(zhí)行結(jié)果在實(shí)現(xiàn)這兩個(gè)核心功能之前,我們先來(lái)改造一下之前實(shí)現(xiàn)的effect函數(shù)。

怎么能使effect函數(shù)變成懶執(zhí)行呢,比如計(jì)算屬性的這種功能,我們不想要他立即執(zhí)行,而是希望在它需要的時(shí)候才執(zhí)行。

這時(shí)候我們可以在effect函數(shù)中傳遞第二個(gè)參數(shù),一個(gè)對(duì)象,用來(lái)設(shè)置一些額外的功能。

functioneffect(fn,options={}){//修改

leteffectFn=function(){

activeEffect=effectFn

effectStack.push(effectFn)

fn()

effectStack.pop()

activeEffect=effectStack[effectStack.length-1]

//只有當(dāng)非lazy的時(shí)候才直接執(zhí)行

if(!options.lazy){

effectFn()

//將依賴函數(shù)組為返回值進(jìn)行返回

returneffectFn//新增

}

這時(shí),如果傳遞了lazy屬性,那么該effect將不會(huì)立即執(zhí)行,需要手動(dòng)進(jìn)行執(zhí)行:

consteffectFn=effect(()={

console.log(obj.foo)

},{lazy:true})

//手動(dòng)執(zhí)行

effectFn()

但是如果我們想要獲取手動(dòng)執(zhí)行后的值呢,這時(shí)只需要在effect函數(shù)中將其返回即可。

functioneffect(fn,options={}){

leteffectFn=function(){

activeEffect=effectFn

effectStack.push(effectFn)

//保存返回值

constres=fn()//新增

effectStack.pop()

activeEffect=effectStack[effectStack.length-1]

returnres//新增

//只有當(dāng)非lazy的時(shí)候才直接執(zhí)行

if(!options.lazy){

effectFn()

//將依賴函數(shù)組為返回值進(jìn)行返回

returneffectFn

}

接下來(lái)開(kāi)始實(shí)現(xiàn)computed函數(shù):

functioncomputed(getter){

//創(chuàng)建一個(gè)可手動(dòng)調(diào)用的依賴函數(shù)

consteffectFn=effect(getter,{

lazy:true

//當(dāng)對(duì)象被訪問(wèn)的時(shí)候才調(diào)用依賴函數(shù)

constobj={

getvalue(){

returneffectFn()

returnobj

}

但是此時(shí)還做不到對(duì)值進(jìn)行緩存和對(duì)比,增加兩個(gè)變量,一個(gè)存儲(chǔ)執(zhí)行的值,另一個(gè)為一個(gè)開(kāi)關(guān),表示是否可以重新執(zhí)行依賴函數(shù):

functioncomputed(getter){

//定義value保存執(zhí)行結(jié)果

//isRun表示是否需要執(zhí)行依賴函數(shù)

letvalue,isRun=true;//新增

consteffectFn=effect(getter,{

lazy:true

constobj={

getvalue(){

//增加判斷,isRun為true時(shí)才會(huì)重新執(zhí)行

if(isRun){//新增

//保存執(zhí)行結(jié)果

value=effectFn()//新增

//執(zhí)行完畢后再次重置執(zhí)行開(kāi)關(guān)

isRun=false//新增

returnvalue

returnobj

}

但是上面的實(shí)現(xiàn)還有一個(gè)問(wèn)題,就是好像isRun執(zhí)行一次后好像永遠(yuǎn)都不會(huì)變成true了,我們的本意是在數(shù)據(jù)發(fā)生變動(dòng)的時(shí)候需要再次觸發(fā)依賴函數(shù),也就是將isRun變?yōu)閠rue,實(shí)現(xiàn)這種效果,需要我們?yōu)閛ptions再傳遞一個(gè)函數(shù),用于用戶自定義的調(diào)度執(zhí)行。

functioneffect(fn,options={}){

leteffectFn=function(){

activeEffect=effectFn

effectStack.push(effectFn)

constres=fn()

effectStack.pop()

activeEffect=effectStack[effectStack.length-1]

returnres

//掛載用戶自定義的調(diào)度執(zhí)行器

effectFn.options=options//新增

if(!options.lazy){

effectFn()

returneffectFn

}

接下來(lái)需要修改一下trigger如果傳遞了scheduler這個(gè)函數(shù),那么只執(zhí)行scheduler這個(gè)函數(shù)而不執(zhí)行依賴函數(shù):

functiontrigger(target,key){

letdepsMap=store.get(target)

if(!depsMap)return

consteffects=depsMap.get(key)

leteffectsToRun=newSet()

effectseffects.forEach(effectFn={

if(effectFn!==activeEffect){

effectsToRun.add(effectFn)

effectsToRun.forEach(effect={

//如果存在調(diào)度器scheduler,那么直接調(diào)用該調(diào)度器,并將依賴函數(shù)進(jìn)行傳遞

if(effectFn.options.scheduler){//新增

effectFn.options.scheduler(effect)//新增

}else{

effect()

}

那么在computed中就可以實(shí)現(xiàn)重置執(zhí)行開(kāi)關(guān)isRun的操作了:

functioncomputed(getter){

//定義value保存執(zhí)行結(jié)果

//isRun表示是否需要執(zhí)行依賴函數(shù)

letvalue,isRun=true;//新增

consteffectFn=effect(getter,{

lazy:true,

scheduler(){

if(!isRun){

isRun=true

constobj={

getvalue(){

//增加判斷,isRun為true時(shí)才會(huì)重新執(zhí)行

if(isRun){//新增

//保存執(zhí)行結(jié)果

value=effectFn()//新增

//執(zhí)行完畢后再次重置執(zhí)行開(kāi)關(guān)

isRun=false//新增

returnvalue

returnobj

}

當(dāng)computed傳入的依賴函數(shù)中的值發(fā)生改變時(shí),會(huì)觸發(fā)響應(yīng)式對(duì)象的trigger函數(shù),而計(jì)算屬性創(chuàng)建響應(yīng)式對(duì)象時(shí)傳入了scheduler,所以當(dāng)數(shù)據(jù)改變時(shí),只會(huì)執(zhí)行scheduler函數(shù),在scheduler函數(shù)內(nèi)我們將執(zhí)行開(kāi)關(guān)重置為true,再下次訪問(wèn)數(shù)據(jù)觸發(fā)get函數(shù)時(shí),就會(huì)重新執(zhí)行依賴函數(shù)。這也就實(shí)現(xiàn)了當(dāng)數(shù)據(jù)發(fā)生改變時(shí),會(huì)再次觸發(fā)依賴函數(shù)的功能了。

為了避免計(jì)算屬性被另外一個(gè)依賴函數(shù)調(diào)用而失去響應(yīng),我們還需要為計(jì)算屬性單獨(dú)進(jìn)行綁定響應(yīng)式的功能,形成一個(gè)effect嵌套。

functioncomputed(getter){

letvalue,isRun=true;

consteffectFn=effect(getter,{

lazy:true,

scheduler(){

if(!isRun){

isRun=true

//當(dāng)計(jì)算屬性依賴的響應(yīng)式數(shù)據(jù)發(fā)生變化時(shí),手動(dòng)調(diào)用trigger函數(shù)觸發(fā)響應(yīng)

trigger(obj,value)//新增

constobj={

getvalue(){

if(isRun){

value=effectFn()

isRun=false

//當(dāng)讀取value時(shí),手動(dòng)調(diào)用track函數(shù)進(jìn)行追蹤

track(obj,value)

returnvalue

returnobj

}

五.watch

先來(lái)看一下watch函數(shù)的用法,它的用法也非常簡(jiǎn)單:

watch(obj,()={

console.log(改變了)

//修改數(shù)據(jù),觸發(fā)watch函數(shù)

obj.age++

watch接受兩個(gè)參數(shù),第一個(gè)參數(shù)為綁定的響應(yīng)式數(shù)據(jù),第二個(gè)參數(shù)為依賴函數(shù),我們依然可以沿用之前的思路來(lái)進(jìn)行處理,利用effect以及scheduler來(lái)改變觸發(fā)執(zhí)行時(shí)機(jī)。

functionwatch(source,fn){

effect(

//遞歸讀取對(duì)象中的每一項(xiàng),變?yōu)轫憫?yīng)式數(shù)據(jù),綁定依賴函數(shù)

()=bindData(source),

scheduler(){

//當(dāng)數(shù)據(jù)發(fā)生改變時(shí),調(diào)用依賴函數(shù)

fn()

//readData保存已讀取過(guò)的數(shù)據(jù),防止重復(fù)讀取

functionbindData(value,readData=newSet()){

//此處只考慮對(duì)象的情況,如果值已被讀取/值不存在/值不為對(duì)象,那么直接返回

if(typeofvalue!==object||value==null||readData.has(value))return

//保存已讀取對(duì)象

readData.add(value)

//遍歷對(duì)象

for(constkeyinvalue){

//遞歸進(jìn)行讀取

bindData(value[key],readData)

returnvalue

}

watch函數(shù)還有另外一種用法,就是除了接收對(duì)象,還可以接受一個(gè)getter函數(shù),例如:

watch(

()=obj.age,

()={

console.log(改變了)

)

這種情況下只需要將用戶傳入的getter將我們自定義的bindData替代即可:

functionwatch(source,fn){

letgetter=typeofsource===functionsource:(()=bindData(source))

effect(

//執(zhí)行g(shù)etter

()=getter(),

scheduler(){

//當(dāng)數(shù)據(jù)發(fā)生改變時(shí),調(diào)用依賴函數(shù)

fn()

}

其實(shí)watch函數(shù)還有一個(gè)很重要的功能:就是在用戶傳遞的依賴函數(shù)中可以獲取新值和舊值,但是我們目前還做不到這一點(diǎn)。實(shí)現(xiàn)這個(gè)功能我們可以配置前文中的lazy屬性來(lái)實(shí)現(xiàn)。來(lái)回顧一下lazy屬性:設(shè)置了lazy之后一開(kāi)始不會(huì)執(zhí)行依賴函數(shù),手動(dòng)執(zhí)行時(shí)會(huì)返回執(zhí)行結(jié)果:

functionwatch(source,fn){

letgetter=typeofsource===functionsource:(()=bindData(source))

//定義新值與舊值

letnewVal,oldVal;//新增

consteffectFn=effect(

//執(zhí)行g(shù)etter

()=getter(),

lazy:true,

scheduler(){

//在scheduler重新執(zhí)行依賴函數(shù),得到新值

newVal=effectFn()//新增

fn(newVal,oldVal)//新增

//執(zhí)行完畢后更新舊值

oldVal=newVal//新增

//手動(dòng)調(diào)用依賴函數(shù),取得舊值

oldVal=effectFn()//新增

}

此外,watch函數(shù)還有一個(gè)功能,就是可以自定義執(zhí)行時(shí)機(jī),比如immediate屬性,他會(huì)在創(chuàng)建時(shí)立即執(zhí)行一次:

watch(obj,()={

console.log(改變了)

immediate:true

})

我們可以把scheduler封裝為一個(gè)函數(shù),以便在不同的時(shí)機(jī)去調(diào)用他:

functionwatch(source,fn,options={}){

letgetter=typeofsource===functionsource:(()=bindData(source))

letnewVal,oldVal;

construn=()={//新增

newVal=effectFn()

fn(newVal,oldVal)

oldVal=newVal

consteffectFn=effect(

()=getter(),

lazy:true,

//使用run來(lái)執(zhí)行依賴函數(shù)

scheduler:run//修改

//當(dāng)immediate為true時(shí),立即執(zhí)行一次依賴函數(shù)

if(options.immediate){//新增

run()//新增

}else{

oldVal=effectFn()

}

watch函數(shù)還支持其他的執(zhí)行調(diào)用時(shí)機(jī),這里只實(shí)現(xiàn)了immediate。

六.淺響應(yīng)與深響應(yīng)

深響應(yīng)和淺響應(yīng)的區(qū)別:

constobj=reatcive({foo:{bar:1}})

effect(()={

console.log(obj.foo.bar)

//修改obj.foo.bar的值,并不能觸發(fā)響應(yīng)

obj.foo.bar=2

因?yàn)橹皩?shí)現(xiàn)的攔截,無(wú)論對(duì)于什么類型的數(shù)據(jù)都是直接進(jìn)行返回的,如果實(shí)現(xiàn)深響應(yīng),那么首先應(yīng)該判斷是否為對(duì)象類型的值,如果是對(duì)象類型的值,應(yīng)當(dāng)遞歸調(diào)用reactive方法進(jìn)行轉(zhuǎn)換。

//接收第二個(gè)參數(shù),標(biāo)記為是否為淺響應(yīng)

functioncreateReactive(obj,isShallow=false){

returnnewProxy(obj,{

get(target,key,receiver){

//訪問(wèn)raw時(shí),返回原對(duì)象

if(key===raw)returntarget

track(target,key)

constres=Reflect.get(target,key,receiver)

//如果是淺響應(yīng),直接返回值

if(isShallow){

returnres

//判斷res是否為對(duì)象并且不為null,循環(huán)調(diào)用reatcive

if(typeofres===objectres!==null){

returnreatcive(res)

returnres

//...省略其他

})

將創(chuàng)建響應(yīng)式對(duì)象的方法抽離出去,通過(guò)傳遞isShallow參數(shù)來(lái)決定是否創(chuàng)建深響應(yīng)/淺響應(yīng)對(duì)象。

//深響應(yīng)

functionreactive(obj){

returncreateReactive(obj)

//淺響應(yīng)

functionshallowReactive(obj){

returncreateReactive(obj,true)

}

七.淺只讀與深只讀

有時(shí)候我們并不需要對(duì)值進(jìn)行修改,也就是需要值為只讀的,這個(gè)操作也分為深只讀和淺只讀,首先需要在createReactive函數(shù)中增加一個(gè)參數(shù)isReadOnly,代表是否為只讀屬性。

//淺只讀

functionshallowReadOnly(obj){

returncreateReactive(obj,true,true)

//深只讀

functionreadOnly(obj){

returncreateReactive(obj,false,true)

}

set(target,key,newValue,receiver){

//是否為只讀屬性,如果是則打印警告信息并直接返回

if(isReadOnly){

console.log(`屬性${key}是只讀的`)

returnfalse

constoldVal=target[key]

consttype=Ototype.hasOwnProperty.call(target,key)triggerType.SET:triggerType.ADD

constres=Reflect.set(target,key,newValue,receiver)

if(target===receiver.raw){

if(oldVal!==newValue(oldVal===oldVal||newValue===newValue)){

trigger(target,key,type)

returnres

}

如果為只讀屬性,那么也不需要為其建立響應(yīng)聯(lián)系如果為只讀屬性,那么在進(jìn)行深層次遍歷的時(shí)候,需要調(diào)用readOnly函數(shù)對(duì)值進(jìn)行包裝

functioncreateReactive(obj,isShallow=false,isReadOnly=false){

returnnewProxy(obj,{

get(target,key,receiver){

//訪問(wèn)raw時(shí),返回原對(duì)象

if(key===raw)returntarget

//只有在非只讀的時(shí)候才需要建立響應(yīng)聯(lián)系

if(!isReadOnly){

track(target,key)

constres=Reflect.get(target,key,receiver)

//如果是淺響應(yīng),直接返回值

if(isShallow){

returnres

//判斷res是否為對(duì)象并且不為null,循環(huán)調(diào)用creative

if(typeofres===objectres!==null){

//如果數(shù)據(jù)為只讀,則調(diào)用readOnly對(duì)值進(jìn)行包裝

returnisReadOnlyreadOnly(res):creative(res)

returnres

}

八.處理數(shù)組

數(shù)組的索引與length

如果操作數(shù)組時(shí),設(shè)置的索引值大于數(shù)組當(dāng)前的長(zhǎng)度,那么要更新數(shù)組的length屬性,所以當(dāng)通過(guò)索引設(shè)置元素值時(shí),可能會(huì)隱式的修改length的屬性值,因此再j進(jìn)行觸發(fā)響應(yīng)時(shí),也應(yīng)該觸發(fā)與length屬性相關(guān)聯(lián)的副作用函數(shù)重新執(zhí)行。

constarr=reactive([foo])//數(shù)組原來(lái)的長(zhǎng)度為1

effect(()={

console.log(arr.length)//1

//設(shè)置索引為1的值,會(huì)導(dǎo)致數(shù)組長(zhǎng)度變?yōu)?

arr[1]=bar

在判斷操作類型時(shí),新增對(duì)數(shù)組類型的判斷,如果代理目標(biāo)是數(shù)組,那么對(duì)于操作類型的判斷作出處理:

如果設(shè)置的索引值小于數(shù)組的長(zhǎng)度,就視為SET操作,因?yàn)樗粫?huì)改變數(shù)組長(zhǎng)度,如果設(shè)置的索引值大于當(dāng)前數(shù)組的長(zhǎng)度,那么應(yīng)該被視為ADD操作。

//定義常量,便于修改

consttriggerType={

ADD:add,

SET:set

set(target,key,newValue,receiver){

if(isReadOnly){

console.log(`屬性${key}是只讀的`)

returnfalse

constoldVal=target[key]

//如果目標(biāo)對(duì)象是數(shù)組,檢測(cè)被設(shè)置的索引值是否小于數(shù)組長(zhǎng)度

consttype=Array.isArray(target)(Number(key)target.lengthtriggerType.ADD:triggerType.SET)

constres=Reflect.set(target,key,newValue,receiver)

trigger(target,key,type)

returnres

},

functiontrigger(target,key,type){

constdepsMap=store.get(target)

if(!depsMap)return

consteffects=depsMap.get(key)

leteffectsToRun=newSet()

effectseffects.forEach(effectFn={

if(effectFn!==activeEffect){

effectsToRun.add(effectFn)

//當(dāng)操作類型是ADD并且目標(biāo)對(duì)象時(shí)數(shù)組時(shí),應(yīng)該取出執(zhí)行那些與length屬性相關(guān)的副作用函數(shù)

if(Array.isArray(target)type===triggerType.ADD){

//取出與length相關(guān)的副作用函數(shù)

constlengthEffects=deps.get(length)

lengthEffectslengthEffects.forEach(effectFn={

if(effectFn!==activeEffect){

effectsToRun.add(effectFn)

effectsToRun.forEach(effect={

if(effectFn.options.scheduler){

effectFn.options.scheduler(effect)

}else{

effect()

}

還有一點(diǎn):其實(shí)修改數(shù)組的length屬性也會(huì)隱式的影響數(shù)組元素:

constarr=reactive([foo])

effect(()={

//訪問(wèn)數(shù)組的第0個(gè)元素

console.log(arrr[0])//foo

//將數(shù)組的長(zhǎng)度修改為0,導(dǎo)致第0個(gè)元素被刪除,因此應(yīng)該觸發(fā)響應(yīng)

arr.length=0

如上所示,在副作用函數(shù)內(nèi)部訪問(wèn)了第0個(gè)元素,然后將數(shù)組的length屬性修改為0,這回隱式的影響數(shù)組元素,及所有的元素都會(huì)被刪除,所以應(yīng)該觸發(fā)副作用函數(shù)重新執(zhí)行。

然而并非所有的對(duì)length屬性值的修改都會(huì)影響數(shù)組中的已有元素,如果設(shè)置的length屬性為100,這并不會(huì)影響第0個(gè)元素,當(dāng)修改屬性值時(shí),只有那些索引值大于等于新的length屬性值的元素才需要觸發(fā)響應(yīng)。

調(diào)用trigger函數(shù)時(shí)傳入新值:

set(target,key,newValue,receiver){

if(isReadOnly){

console.log(`屬性${key}是只讀的`)

returnfalse

constoldVal=target[key]

//如果目標(biāo)對(duì)象是數(shù)組,檢測(cè)被設(shè)置的索引值是否小于數(shù)組長(zhǎng)度

consttype=Array.isArray(target)(Number(key)target.lengthtriggerType.ADD:triggerType.SET)

constres=Reflect.set(target,key,newValue,receiver)

//將新的值進(jìn)行傳遞,及觸發(fā)響應(yīng)的新值

trigger(target,key,type,newValue)//新增

returnres

}

判斷新的下標(biāo)值與需要操作的新的下標(biāo)值進(jìn)行判斷,因?yàn)閿?shù)組的key為下標(biāo),所以副作用函數(shù)搜集器是以下標(biāo)作為key值的,當(dāng)length發(fā)生變動(dòng)時(shí),只需要將新值與每個(gè)下標(biāo)的key判斷,大于等于新的length值的需要重新執(zhí)行副作用函數(shù)。

如上圖所示,Map為根據(jù)數(shù)組的key,也就是id組成的Map結(jié)構(gòu),他們的每一個(gè)key都對(duì)應(yīng)一個(gè)Set,用于保存這個(gè)key下面的所有的依賴函數(shù)。

當(dāng)length屬性發(fā)生變動(dòng)時(shí),應(yīng)當(dāng)取出所有key值大于等于length值的所有依賴函數(shù)進(jìn)行執(zhí)行。

functiontrigger(target,key,type,newValue){

constdepsMap=store.get(target)

if(!depsMap)return

consteffects=depsMap.get(key)

leteffectsToRun=newSet()

effectseffects.forEach(effectFn={

if(effectFn!==activeEffect){

effectsToRun.add(effectFn)

//如果操作目標(biāo)是數(shù)組,并且修改了數(shù)組的length屬性

if(Array.isArray(target)key===length){

//對(duì)于索引值大于或等于新的length元素

//需要把所有相關(guān)聯(lián)的副作用函數(shù)取出并添加到effectToRun中待執(zhí)行

depsMap.forEach((effects,key)={

//key與newValue均為數(shù)組下標(biāo),因?yàn)閿?shù)組中key為index

if(key=newValue){

effects.forEach(effectFn={

if(effectFn!==activeEffect){

effectsToRun.add(effectFn)

//...省略

}

本文的實(shí)現(xiàn)數(shù)組這種數(shù)據(jù)結(jié)構(gòu)只考慮了針對(duì)長(zhǎng)度發(fā)生變化的情況。

九.ref

由于Proxy的代理目標(biāo)是非原始值,所以沒(méi)有任何手段去攔截對(duì)原始值的操作:

letstr=hi

//無(wú)法攔截對(duì)值的修改

str=pino

解決方法是:使用一個(gè)非原始值去包裹

溫馨提示

  • 1. 本站所有資源如無(wú)特殊說(shuō)明,都需要本地電腦安裝OFFICE2007和PDF閱讀器。圖紙軟件為CAD,CAXA,PROE,UG,SolidWorks等.壓縮文件請(qǐng)下載最新的WinRAR軟件解壓。
  • 2. 本站的文檔不包含任何第三方提供的附件圖紙等,如果需要附件,請(qǐng)聯(lián)系上傳者。文件的所有權(quán)益歸上傳用戶所有。
  • 3. 本站RAR壓縮包中若帶圖紙,網(wǎng)頁(yè)內(nèi)容里面會(huì)有圖紙預(yù)覽,若沒(méi)有圖紙預(yù)覽就沒(méi)有圖紙。
  • 4. 未經(jīng)權(quán)益所有人同意不得將文件中的內(nèi)容挪作商業(yè)或盈利用途。
  • 5. 人人文庫(kù)網(wǎng)僅提供信息存儲(chǔ)空間,僅對(duì)用戶上傳內(nèi)容的表現(xiàn)方式做保護(hù)處理,對(duì)用戶上傳分享的文檔內(nèi)容本身不做任何修改或編輯,并不能對(duì)任何下載內(nèi)容負(fù)責(zé)。
  • 6. 下載文件中如有侵權(quán)或不適當(dāng)內(nèi)容,請(qǐng)與我們聯(lián)系,我們立即糾正。
  • 7. 本站不保證下載資源的準(zhǔn)確性、安全性和完整性, 同時(shí)也不承擔(dān)用戶因使用這些下載資源對(duì)自己和他人造成任何形式的傷害或損失。

評(píng)論

0/150

提交評(píng)論