欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

淺析Proxy如何實(shí)現(xiàn)Vue響應(yīng)式

 更新時(shí)間:2023年08月04日 09:38:27   作者:前端胖頭魚(yú)  
這篇文章主要是來(lái)和大家探討一下,Vue的響應(yīng)式系統(tǒng)僅僅是一個(gè)Proxy嗎,本文將圍繞此問(wèn)題探索一下Proxy是如何實(shí)現(xiàn)Vue響應(yīng)式的,感興趣的小伙伴可以了解一下

前言

在面試官:Vue3響應(yīng)式系統(tǒng)都不會(huì)寫(xiě),還敢說(shuō)精通?中我們實(shí)現(xiàn)了一個(gè)最基本的響應(yīng)式系統(tǒng)。

它包含以下功能:

  • 借助Proxy將一個(gè)對(duì)象obj變成響應(yīng)式數(shù)據(jù),攔截其get和set操作。
  • 通過(guò)effect注冊(cè)副作用函數(shù),并在首次執(zhí)行副作用函數(shù)時(shí)完成obj對(duì)象的依賴收集(track)。
  • 當(dāng)數(shù)據(jù)發(fā)生變化的時(shí)候,第2步注冊(cè)的副作用函數(shù)會(huì)重新執(zhí)行(trigger)。

回顧源碼

const?bucket?=?new?WeakMap()
//?重新定義bucket數(shù)據(jù)類型為WeakMap
let?activeEffect
const?effect?=?function?(fn)?{
??activeEffect?=?fn
??fn()
}
//?track表示追蹤的意思
function?track?(target,?key)?{
??//?activeEffect無(wú)值意味著沒(méi)有執(zhí)行effect函數(shù),無(wú)法收集依賴,直接return掉
??if?(!activeEffect)?{
????return
??}
??//?每個(gè)target在bucket中都是一個(gè)Map類型:?key?=>?effects
??let?depsMap?=?bucket.get(target)
??//?第一次攔截,depsMap不存在,先創(chuàng)建聯(lián)系
??if?(!depsMap)?{
????bucket.set(target,?(depsMap?=?new?Map()))
??}
??//?根據(jù)當(dāng)前讀取的key,嘗試讀取key的effects函數(shù)??
??let?deps?=?depsMap.get(key)
??if?(!deps)?{
????//?deps本質(zhì)是個(gè)Set結(jié)構(gòu),即一個(gè)key可以存在多個(gè)effect函數(shù),被多個(gè)effect所依賴
????depsMap.set(key,?(deps?=?new?Set()))
??}
??//?將激活的effectFn存進(jìn)桶中
??deps.add(activeEffect)
}
//?trigger執(zhí)行依賴
function?trigger?(target,?key)?{
??//?讀取depsMap?其結(jié)構(gòu)是?key?=>?effects
??const?depsMap?=?bucket.get(target)
??if?(!depsMap)?{
????return
??}
??//?真正讀取依賴當(dāng)前屬性值key的effects
??const?effects?=?depsMap.get(key)
??//?挨個(gè)執(zhí)行即可
??effects?&&?effects.forEach((fn)?=>?fn())
}
//?統(tǒng)一對(duì)外暴露響應(yīng)式函數(shù)
function?reactive?(state)?{
??return?new?Proxy(state,?{
????get?(target,?key)?{
??????const?value?=?target[?key?]
??????track(target,?key)
??????//?console.log(`get?${key}:?${value}`)
??????return?value
????},
????set?(target,?key,?newValue)?{
??????//?console.log(`set?${key}:?${newValue}`)
??????//?設(shè)置屬性值
??????target[?key?]?=?newValue
??????trigger(target,?key)
????}
??})
}

測(cè)試一下

const?state?=?reactive({
??name:?'fatfish',
??age:?100
})
//?effect1
effect(()?=>?{
??console.log(state.name,?'name')
})
//?effect2
effect(()?=>?{
??console.log(state.age,?'age')
})
state.name?=?'fatfish2'?//?因?yàn)閚ame屬性發(fā)生變化了,effect1將會(huì)重新執(zhí)行,打印出的name是fatfish2

看起來(lái)還不錯(cuò),不過(guò)他還存在很多缺陷和不足,比如:

  • 分支切換會(huì)導(dǎo)致不必要的effect執(zhí)行損耗
  • effect不支持嵌套注冊(cè)副作用函數(shù)
  • ...

咱們挨個(gè)看看,這都是些啥...

支持分支切換

什么是分支切換?

按照上文的結(jié)論,這段代碼執(zhí)行后會(huì)形成這樣的數(shù)據(jù)結(jié)構(gòu)。

state
  |___ok
    |___ effectFn
  |___text
    |___ effectFn

const?state?=?reactive({
??ok:?true,
??text:?'hello?world',
});
effect(()?=>?{
??console.log('渲染執(zhí)行')
??document.querySelector('#app').innerHTML?=?state.ok???state.text?:?'not'
})

當(dāng)我們把ok的值改成false后,頁(yè)面將渲染為"not"。意味著后續(xù)無(wú)論text如何變化,頁(yè)面都永遠(yuǎn)只可能是"not"。

所以當(dāng)我們修改text的值時(shí),副作用函數(shù)重新執(zhí)行是沒(méi)有必要的。

const?state?=?reactive({
??ok:?true,
??text:?'hello?world',
});
effect(()?=>?{
??console.log('渲染執(zhí)行')
??document.querySelector('#app').innerHTML?=?state.ok???state.text?:?'not'
})
setTimeout(()?=>?{
??state.ok?=?false?//?此時(shí)頁(yè)面變成了not
??setTimeout(()?=>?{
????state.text?=?'other'?//?頁(yè)面依然是not,但是副作用函數(shù)卻還會(huì)執(zhí)行一次
??},?1000)
},?1000)

如何解決?

修改state.text,副作用函數(shù)會(huì)執(zhí)行是因?yàn)?code>state與其形成的數(shù)據(jù)結(jié)構(gòu)是這樣的。

state
  |___ok
    |___ effectFn
  |___text
    |___ effectFn

如果希望state.text的改動(dòng)effectFn不再執(zhí)行,我們就要想辦法改變這個(gè)結(jié)構(gòu)。

state
  |___ok
    |___ effectFn

此時(shí)無(wú)論你怎樣修改state.text,effectFn都不會(huì)執(zhí)行,因?yàn)樗麄儌z之間并沒(méi)有形成依賴關(guān)系。

在副作用函數(shù)執(zhí)行前先將其從與該副作用函數(shù)有關(guān)的依賴集合中刪除怎么樣?

比如前面的例子,形成了:

state
  |___ok
    |___ effectFn
  |___text
    |___ effectFn

當(dāng)我們修改state.ok = false時(shí),effectFn將會(huì)被執(zhí)行,在執(zhí)行前,我們將effectFn從與之相關(guān)的依賴集合中刪除,最終形成了一個(gè)光桿司令。

state

但是不要忘記,effectFn的重新執(zhí)行,又會(huì)觸發(fā)一次依賴收集,結(jié)束后,數(shù)據(jù)結(jié)構(gòu)會(huì)變成:

state
  |___ok
    |___ effectFn

為了支持這樣的特性,我們需要簡(jiǎn)單的改一下effecttrigger函數(shù).

const?effect?=?function?(fn)?{
??const?effectFn?=?()?=>?{
????cleanup(effectFn)
????activeEffect?=?effectFn
????fn()
??}
??//?用來(lái)存儲(chǔ)哪些依賴集合包含這個(gè)副作用函數(shù)
??effectFn.deps?=?[]
??effectFn()
}
function?cleanup?(effectFn)?{
??for?(let?i?=?0;?i?<?effectFn.deps.length;?i++)?{
????const?deps?=?effectFn.deps[i]
????deps.delete(effectFn)
??}
??effectFn.deps.length?=?0
}

trigger

//?trigger執(zhí)行依賴
function?trigger(target,?key)?{
??//?讀取depsMap?其結(jié)構(gòu)是?key?=>?effects
??const?depsMap?=?bucket.get(target);
??if?(!depsMap)?{
????return;
??}
??//?真正讀取依賴當(dāng)前屬性值key的effects
??const?effects?=?depsMap.get(key);
??//?解決cleanup?執(zhí)行會(huì)無(wú)限執(zhí)行的問(wèn)題
??const?effectsToRun?=?new?Set(effects)
??//?挨個(gè)執(zhí)行即可
??effectsToRun.forEach((fn)?=>?fn());
}

最后測(cè)試一把

const?state?=?reactive({
??ok:?true,
??text:?'hello?world',
});
effect(()?=>?{
??console.log('渲染執(zhí)行')
??document.querySelector('#app').innerHTML?=?state.ok???state.text?:?'not'
})
setTimeout(()?=>?{
??state.ok?=?false?//?頁(yè)面渲染為not
??setTimeout(()?=>?{
????state.text?=?'other'?//?頁(yè)面依然是not,但是副作用函數(shù)不會(huì)再執(zhí)行。
??},?1000)
},?1000)

支持effect嵌套

為什么要支持effect嵌套

先說(shuō)結(jié)論:因?yàn)榻M件是可以嵌套的,而Vue組件又恰巧是在effect中執(zhí)行的。

來(lái)看看Vue中的組件是怎么執(zhí)行的。

const?Foo?=?{
??render?()?{
????return?//?....
??}
}
effect(()?=>?{
??Foo.render()
})

而當(dāng)組件發(fā)生嵌套時(shí),就會(huì)存在effect嵌套:

const?Bar?=?{
??render?()?{
????return?//?....
??}
}
const?Foo?=?{
??render?()?{
????return?<Bar?/>?//?...
??}
}

最后會(huì)變成這樣:

effect(()?=>?{
??Foo.render()
??effect(()?=>?{
????Bar.render()
??})
})

目前的effect存在什么問(wèn)題

先來(lái)試試看目前它的問(wèn)題是什么?。?!

const?state?=?reactive({
??foo:?true,
??bar:?true
})
effect(function?effectFn1?()?{
??console.log('effectFn1')
??effect(function?effectFn2?()?{
????console.log('effectFn2')
????console.log('Bar',?state.bar)
??})
??console.log('Foo',?state.foo)
})

根據(jù)上一篇文章的結(jié)論,我們認(rèn)為響應(yīng)式數(shù)據(jù)state與副作用函數(shù)應(yīng)該會(huì)形成這種數(shù)據(jù)結(jié)構(gòu):

state
  |___foo
    |___ effectFn1
  |___bar
    |___ effectFn2 

所以首次執(zhí)行時(shí)會(huì)打印出這兩行信息:

當(dāng)我們分別修改foo和bar屬性時(shí)會(huì)發(fā)生什么?

修改bar

effectFn2會(huì)重新執(zhí)行。

const?state?=?reactive({
??foo:?true,
??bar:?true
})
effect(function?effectFn1?()?{
??console.log('effectFn1')
??effect(function?effectFn2?()?{
????console.log('effectFn2')
????console.log('Bar',?state.bar)
??})
??console.log('Foo',?state.foo)
})
setTimeout(()?=>?{
??state.bar?=?false
},?1000)

修改foo

effectFn1會(huì)重新執(zhí)行,而effectFn2因?yàn)楸黄淝短姿詴?huì)被間接執(zhí)行。 然而現(xiàn)實(shí)終歸會(huì)告訴我們生活沒(méi)那么美好.

const?state?=?reactive({
??foo:?true,
??bar:?true
})
effect(function?effectFn1?()?{
??console.log('effectFn1')
??effect(function?effectFn2?()?{
????console.log('effectFn2')
????console.log('Bar',?state.bar)
??})
??console.log('Foo',?state.foo)
})
setTimeout(()?=>?{
??state.foo?=?false
},?1000)

所以本質(zhì)上形成了這樣的數(shù)據(jù)結(jié)構(gòu),以至于改變foo的值調(diào)用的是effectFn2。

state
  |___foo
    |___ effectFn2
  |___bar
    |___ effectFn2 

問(wèn)題出在哪里?

當(dāng)effectFn1開(kāi)始執(zhí)行的時(shí),activeEffect指向的是effectFn1。而effectFn1的執(zhí)行會(huì)間接地導(dǎo)致effectFn2的執(zhí)行,此時(shí)activeEffect指向的是effectFn2。

const?effect?=?function?(fn)?{
??const?effectFn?=?()?=>?{
????cleanup(effectFn)
????//?問(wèn)題點(diǎn)~~~
????activeEffect?=?effectFn
????fn()
??}
??//?用來(lái)存儲(chǔ)哪些依賴集合包含這個(gè)副作用函數(shù)
??effectFn.deps?=?[]
??effectFn()
}

當(dāng)effectFn2執(zhí)行完畢時(shí),因?yàn)閍ctiveEffect指向的是effectFn2。所以foo自然也就是和effectFn2建立了聯(lián)系,而不是我們期待的effectFn1。

effect(function?effectFn1?()?{
??console.log('effectFn1')
??effect(function?effectFn2?()?{
????console.log('effectFn2')
????console.log('Bar',?state.bar)
??})
??console.log('Foo',?state.foo)
})

要解決這個(gè)問(wèn)題也很簡(jiǎn)單,我們新維護(hù)一個(gè)注冊(cè)副作用函數(shù)的棧,讓activeEffect指向的是永遠(yuǎn)是棧頂?shù)母弊饔煤瘮?shù)。用上面例子來(lái)模擬一下這個(gè)過(guò)程。

//?第1步:effectFn1執(zhí)行入棧
//?effectFn1?←?activeEffect
//?第2步:effectFn2執(zhí)行入棧
/*
??此時(shí)棧變成了
??effectFn2?←activeEffect
??effectFn1
*/
//?第3步:effectFn2執(zhí)行完畢,將effectFn2出棧處理
//?effectFn1?←activeEffect
//?第4步:effectFn1執(zhí)行完畢,將effectFn1出棧處理
//?此時(shí)棧已是空的

所以我們很容易對(duì)effect做出以下改造:

const?bucket?=?new?WeakMap();
const?effectStack?=?[]
//?重新定義bucket數(shù)據(jù)類型為WeakMap
let?activeEffect;
const?effect?=?function?(fn)?{
??const?effectFn?=?()?=>?{
????cleanup(effectFn)
????activeEffect?=?effectFn
????//?入棧
????effectStack.push(effectFn)
????fn()
????//?出棧
????effectStack.pop()
????activeEffect?=?effectStack[?effectStack.length?-?1?]
??}
??//?用來(lái)存儲(chǔ)哪些依賴集合包含這個(gè)副作用函數(shù)
??effectFn.deps?=?[]
??effectFn()
??console.log(effectStack.length,?'---')
??//?非常重要
??//?activeEffect?=?null
};

再測(cè)試一下上面的例子,一秒鐘后成功的打印了effectFn1和effectFn2

const?state?=?reactive({
??foo:?true,
??bar:?true
})
effect(function?effectFn1?()?{
??console.log('effectFn1')
??effect(function?effectFn2?()?{
????console.log('effectFn2')
????console.log('Bar',?state.bar)
??})
??console.log('Foo',?state.foo)
})
setTimeout(()?=>?{
??state.foo?=?false
},?1000)

到此這篇關(guān)于淺析Proxy如何實(shí)現(xiàn)Vue響應(yīng)式的文章就介紹到這了,更多相關(guān)Vue Proxy響應(yīng)式內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

最新評(píng)論