一文帶你搞懂Vue3.5的響應(yīng)式重構(gòu)
前言
在Vue3.5版本中最大的改動(dòng)就是響應(yīng)式重構(gòu),重構(gòu)后性能竟然炸裂的提升了56%。之所以重構(gòu)后的響應(yīng)式性能提升幅度有這么大,主要還是歸功于:雙向鏈表和版本計(jì)數(shù)。這篇文章我們來(lái)講講使用雙向鏈表后,Vue內(nèi)部是如何實(shí)現(xiàn)依賴收集和依賴觸發(fā)的。搞懂了這個(gè)之后你就能掌握Vue3.5重構(gòu)后的響應(yīng)式原理。
3.5版本以前的響應(yīng)式
在Vue3.5以前的響應(yīng)式中主要有兩個(gè)角色:Sub
(訂閱者)、Dep
(依賴)。其中的訂閱者有watchEffect、watch、render函數(shù)、computed等。依賴有ref、reactive等響應(yīng)式變量。
舉個(gè)例子:
<script setup lang="ts"> import { ref, watchEffect } from "vue"; let dummy1, dummy2; //Dep1 const counter1 = ref(1); //Dep2 const counter2 = ref(2); //Sub1 watchEffect(() => { dummy1 = counter1.value + counter2.value; console.log("dummy1", dummy1); }); //Sub2 watchEffect(() => { dummy2 = counter1.value + counter2.value + 1; console.log("dummy2", dummy2); }); counter1.value++; counter2.value++; </script>
在上面的兩個(gè)watchEffect中都會(huì)去監(jiān)聽(tīng)ref響應(yīng)式變量:counter1
和counter2
。
初始化時(shí)會(huì)分別執(zhí)行這兩個(gè)watchEffect中的回調(diào)函數(shù),所以就會(huì)對(duì)里面的響應(yīng)式變量counter1
和counter2
進(jìn)行讀操作
,所以就會(huì)走到響應(yīng)式變量的get攔截中。
在get攔截中會(huì)進(jìn)行依賴收集(此時(shí)的Dep依賴分別是變量counter1
和counter2
)。
因?yàn)樵谝蕾囀占陂g是在執(zhí)行watchEffect
中的回調(diào)函數(shù),所以依賴對(duì)應(yīng)的Sub訂閱者
就是watchEffect。
由于這里有兩個(gè)watchEffect,所以這里有兩個(gè)Sub訂閱者
,分別對(duì)應(yīng)這兩個(gè)watchEffect。
在上面的例子中,watchEffect監(jiān)聽(tīng)了多個(gè)ref變量。也就是說(shuō),一個(gè)Sub訂閱者
(也就是一個(gè)watchEffect)可以訂閱多個(gè)依賴。
ref響應(yīng)式變量counter1
被多個(gè)watchEffect給監(jiān)聽(tīng)。也就是說(shuō),一個(gè)Dep依賴
(也就是counter1
變量)可以被多個(gè)訂閱者給訂閱。
Sub訂閱者和Dep依賴他們兩的關(guān)系是多對(duì)多的關(guān)系?。。?/p>
上面這個(gè)就是以前的響應(yīng)式模型。
新的響應(yīng)式模型
在Vue3.5版本新的響應(yīng)式中,Sub訂閱者和Dep依賴之間不再有直接的聯(lián)系,而是新增了一個(gè)Link作為橋梁。Sub訂閱者通過(guò)Link訪問(wèn)到Dep依賴,同理Dep依賴也是通過(guò)Link訪問(wèn)到Sub訂閱者。如下圖:
把上面這個(gè)圖看懂了,你就能理解Vue新的響應(yīng)式系統(tǒng)啦?,F(xiàn)在你直接看這個(gè)圖有可能看不懂,沒(méi)關(guān)系,等我講完后你就能看懂了。
首先從上圖中可以看到Sub訂閱者和Dep依賴之間沒(méi)有任何直接的連接關(guān)系了,也就是說(shuō)Sub訂閱者不能直接訪問(wèn)到Dep依賴,Dep依賴也不能直接訪問(wèn)Sub訂閱者。
Dep依賴我們可以看作是X軸,Sub訂閱者可以看作是Y軸,這些Link就是坐標(biāo)軸上面的坐標(biāo)。
Vue響應(yīng)式系統(tǒng)的核心還是沒(méi)有變,只是多了一個(gè)Link,依然還是以前的那一套依賴收集和依賴觸發(fā)的流程。
在依賴收集的過(guò)程中就會(huì)畫出上面這個(gè)圖,這個(gè)不要急,我接下來(lái)會(huì)仔細(xì)去講圖是如何畫出來(lái)的。
那么依賴觸發(fā)的時(shí)候又是如何利用上面這種圖從而實(shí)現(xiàn)觸發(fā)依賴的呢?我們來(lái)看個(gè)例子。
上面的這張圖其實(shí)對(duì)應(yīng)的是我之前舉的例子:
<script setup lang="ts"> import { ref, watchEffect } from "vue"; let dummy1, dummy2; //Dep1 const counter1 = ref(1); //Dep2 const counter2 = ref(2); //Sub1 watchEffect(() => { dummy1 = counter1.value + counter2.value; console.log("dummy1", dummy1); }); //Sub2 watchEffect(() => { dummy2 = counter1.value + counter2.value + 1; console.log("dummy2", dummy2); }); counter1.value++; counter2.value++; </script>
圖中的Dep1依賴
對(duì)應(yīng)的就是變量counter1
,Dep2依賴
對(duì)應(yīng)的就是變量counter2
。Sub1訂閱者
對(duì)應(yīng)的就是第一個(gè)watchEffect
函數(shù),Sub2訂閱者
對(duì)應(yīng)的就是第二個(gè)watchEffect
函數(shù)。
當(dāng)執(zhí)行counter1.value++
時(shí),就會(huì)被變量counter1
(也就是Dep1依賴
)的set函數(shù)攔截。從上圖中可以看到Dep1依賴
有個(gè)箭頭(對(duì)照表中的sub
屬性)指向Link3
,并且Link3
也有一個(gè)箭頭(對(duì)照表中的sub
屬性)指向Sub2
。
前面我們講過(guò)了這個(gè)Sub2
就是對(duì)應(yīng)的第二個(gè)watchEffect
函數(shù),指向Sub2
后我們就可以執(zhí)行Sub2
中的依賴,也就是執(zhí)行第二個(gè)watchEffect
函數(shù)。這就實(shí)現(xiàn)了counter1.value++
變量改變后,重新執(zhí)行第二個(gè)watchEffect
函數(shù)。
執(zhí)行了第二個(gè)watchEffect
函數(shù)后我們發(fā)現(xiàn)Link3
在Y軸上面還有一個(gè)箭頭(對(duì)照表中的preSub
屬性)指向了Link1
。同理Link1
也有一個(gè)箭頭(對(duì)照表中的sub
屬性)指向了Sub1
。
前面我們講過(guò)了這個(gè)Sub1
就是對(duì)應(yīng)的第一個(gè)watchEffect
函數(shù),指向Sub1
后我們就可以執(zhí)行Sub1
中的依賴,也就是執(zhí)行第一個(gè)watchEffect
函數(shù)。這就實(shí)現(xiàn)了counter1.value++
變量改變后,重新執(zhí)行第一個(gè)watchEffect
函數(shù)。
至此我們就實(shí)現(xiàn)了counter1.value++
變量改變后,重新去執(zhí)行依賴他的兩個(gè)watchEffect
函數(shù)。
我們此時(shí)再來(lái)回顧一下我們前面畫的新的響應(yīng)式模型圖,如下圖:
我們從這張圖來(lái)總結(jié)一下依賴觸發(fā)的的規(guī)則:
響應(yīng)式變量Dep1
改變后,首先會(huì)指向Y軸(Sub訂閱者
)的隊(duì)尾
的Link節(jié)點(diǎn)。然后從Link節(jié)點(diǎn)可以直接訪問(wèn)到Sub訂閱者,訪問(wèn)到訂閱者后就可以觸發(fā)其依賴,這里就是重新執(zhí)行對(duì)應(yīng)的watchEffect
函數(shù)。
接著就是順著Y軸的隊(duì)尾
向隊(duì)頭
移動(dòng),每移動(dòng)到一個(gè)新的Link節(jié)點(diǎn)都可以指向一個(gè)新的Dep依賴,在這里觸發(fā)其依賴就會(huì)重新指向?qū)?yīng)的watchEffect
函數(shù)。
看到這里有的同學(xué)有疑問(wèn)如果是Dep2
對(duì)應(yīng)的響應(yīng)式變量改變后指向Link4
,那這個(gè)Link4
又是怎么指向Sub2
的呢?他們中間不是還隔了一個(gè)Link3
嗎?
每一個(gè)Link節(jié)點(diǎn)上面都有一個(gè)sub
屬性直接指向Y軸上面的Sub依賴,所以這里的Link4
有個(gè)箭頭(對(duì)照表中的sub
屬性)可以直接指向Sub2
,然后進(jìn)行依賴觸發(fā)。
這就是Vue3.5版本使用雙向鏈表
改進(jìn)后的依賴觸發(fā)原理,接下來(lái)我們會(huì)去講依賴收集過(guò)程中是如何將上面的模型圖畫出來(lái)的。
Dep、Sub和Link
在講Vue3.5版本依賴收集之前,我們先來(lái)了解一下新的響應(yīng)式系統(tǒng)中主要的三個(gè)角色:Dep依賴、Sub訂閱者、Link節(jié)點(diǎn)。
這三個(gè)角色其實(shí)都是class類,依賴收集和依賴觸發(fā)的過(guò)程中實(shí)際就是在操作這些類new出來(lái)的的對(duì)象。
我們接下來(lái)看看這些類中有哪些屬性和方法,其實(shí)在前面的響應(yīng)式模型圖中我們已經(jīng)使用箭頭標(biāo)明了這些類上面的屬性。
Dep依賴
簡(jiǎn)化后的Dep
類定義如下:
class Dep { // 指向Link鏈表的尾部節(jié)點(diǎn) subs: Link // 收集依賴 track: Function // 觸發(fā)依賴 trigger: Function }
Dep依賴上面的subs
屬性就是指向隊(duì)列的尾部
,也就是隊(duì)列中最后一個(gè)Sub訂閱者對(duì)應(yīng)的Link節(jié)點(diǎn)。
比如這里的Dep1
,豎向的Link1
和Link3
就組成了一個(gè)隊(duì)列。其中Link3
是隊(duì)列的隊(duì)尾,Dep1
的subs
屬性就是指向Link3
。
其次就是track
函數(shù),對(duì)響應(yīng)式變量進(jìn)行讀操作時(shí)會(huì)觸發(fā)。觸發(fā)這個(gè)函數(shù)后會(huì)進(jìn)行依賴收集,后面我會(huì)講。
同樣trigger
函數(shù)用于依賴觸發(fā),對(duì)響應(yīng)式變量進(jìn)行寫操作時(shí)會(huì)觸發(fā),后面我也會(huì)講。
Sub訂閱者
簡(jiǎn)化后的Sub
訂閱者定義如下:
interface Subscriber { // 指向Link鏈表的頭部節(jié)點(diǎn) deps: Link // 指向Link鏈表的尾部節(jié)點(diǎn) depsTail: Link // 執(zhí)行依賴 notify: Function }
想必細(xì)心的你發(fā)現(xiàn)了這里的Subscriber
是一個(gè)interface
接口,而不是一個(gè)class類。因?yàn)閷?shí)現(xiàn)了這個(gè)Subscriber
接口的class類都是訂閱者,比如watchEffect、watch、render函數(shù)、computed等。
比如這里的Sub1
,橫向的Link1
和Link2
就組成一個(gè)隊(duì)列。其中的隊(duì)尾就是Link2
(depsTail
屬性),隊(duì)頭就是Link1
(deps
屬性)。
還有就是notify
函數(shù),執(zhí)行這個(gè)函數(shù)就是在執(zhí)行依賴。比如對(duì)于watchEffect來(lái)說(shuō),執(zhí)行notify
函數(shù)后就會(huì)執(zhí)行watchEffect的回調(diào)函數(shù)。
Link節(jié)點(diǎn)
簡(jiǎn)化后的Link
節(jié)點(diǎn)類定義如下:
class Link { // 指向Subscriber訂閱者 sub: Subscriber // 指向Dep依賴 dep: Dep // 指向Link鏈表的后一個(gè)節(jié)點(diǎn)(X軸) nextDep: Link // 指向Link鏈表的前一個(gè)節(jié)點(diǎn)(X軸) prevDep: Link // 指向Link鏈表的下一個(gè)節(jié)點(diǎn)(Y軸) nextSub: Link // 指向Link鏈表的上一個(gè)節(jié)點(diǎn)(Y軸) prevSub: Link }
前面我們講過(guò)了新的響應(yīng)式模型中Dep依賴
和Sub訂閱者
之間不會(huì)再有直接的關(guān)聯(lián),而是通過(guò)Link作為橋梁。
那么作為橋梁的Link節(jié)點(diǎn)肯定需要有兩個(gè)屬性能夠讓他直接訪問(wèn)到Dep依賴
和Sub訂閱者
,也就是sub
和dep
屬性。
其中的sub
屬性是指向Sub訂閱者
,dep
屬性是指向Dep依賴
。
我們知道Link是坐標(biāo)軸的點(diǎn),那這個(gè)點(diǎn)肯定就會(huì)有上、下、左、右四個(gè)方向。
比如對(duì)于Link1
可以使用nextDep
屬性來(lái)訪問(wèn)后面這個(gè)節(jié)點(diǎn)Link2
,Link2
可以使用prevDep
屬性來(lái)訪問(wèn)前面這個(gè)節(jié)點(diǎn)Link1
。
請(qǐng)注意,這里名字雖然叫nextDep
和prevDep
,但是他們指向的卻是Link節(jié)點(diǎn)。然后通過(guò)這個(gè)Link節(jié)點(diǎn)的dep
屬性,就可以訪問(wèn)到后一個(gè)Dep依賴
或者前一個(gè)Dep依賴
。
同理對(duì)于Link1
可以使用nextSub
訪問(wèn)后面這個(gè)節(jié)點(diǎn)Link3
,Link3
可以使用prevSub
訪問(wèn)前面這個(gè)節(jié)點(diǎn)Link1
。
同樣的這里名字雖然叫nextSub
和prevSub
,但是他們指向的卻是Link節(jié)點(diǎn)。然后通過(guò)這個(gè)Link節(jié)點(diǎn)的sub
屬性,就可以訪問(wèn)到下一個(gè)Sub訂閱者
或者上一個(gè)Sub訂閱者
。
如何收集依賴
搞清楚了新的響應(yīng)式模型中的三個(gè)角色:Dep依賴
、Sub訂閱者
、Link節(jié)點(diǎn)
,我們現(xiàn)在就可以開(kāi)始搞清楚新的響應(yīng)式模型是如何收集依賴的。
接下來(lái)我將會(huì)帶你如何一步步的畫出前面講的那張新的響應(yīng)式模型圖。
還是我們前面的那個(gè)例子,代碼如下:
<script setup lang="ts"> import { ref, watchEffect } from "vue"; let dummy1, dummy2; //Dep1 const counter1 = ref(1); //Dep2 const counter2 = ref(2); //Sub1 watchEffect(() => { dummy1 = counter1.value + counter2.value; console.log("dummy1", dummy1); }); //Sub2 watchEffect(() => { dummy2 = counter1.value + counter2.value + 1; console.log("dummy2", dummy2); }); counter1.value++; counter2.value++; </script>
大家都知道響應(yīng)式變量有get
和set
攔截,當(dāng)對(duì)變量進(jìn)行讀操作時(shí)會(huì)走到get
攔截中,進(jìn)行寫操作時(shí)會(huì)走到set
攔截中。
上面的例子第一個(gè)watchEffect
我們叫做Sub1
訂閱者,第二個(gè)watchEffect
叫做Sub2
訂閱者.
初始化時(shí)watchEffect
中的回調(diào)會(huì)執(zhí)行一次,這里有兩個(gè)watchEffect
,會(huì)依次去執(zhí)行。
在Vue內(nèi)部有個(gè)全局變量叫activeSub
,里面存的是當(dāng)前active的Sub訂閱者。
執(zhí)行第一個(gè)watchEffect
回調(diào)時(shí),當(dāng)前的activeSub
就是Sub1
。
在Sub1
中使用到了響應(yīng)式變量counter1
和counter2
,所以會(huì)對(duì)這兩個(gè)變量依次進(jìn)行讀操作。
第一個(gè)watchEffect對(duì)counter1進(jìn)行讀操作
先對(duì)counter1
進(jìn)行讀操作時(shí),會(huì)走到get
攔截中。核心代碼如下:
class RefImpl { get value() { this.dep.track(); return this._value; } }
從上面可以看到在get攔截中直接調(diào)用了dep依賴的track
方法進(jìn)行依賴收集。
在執(zhí)行track
方法之前我們思考一下當(dāng)前響應(yīng)式系統(tǒng)中有哪些角色,分別是Sub1
和Sub2
這兩個(gè)watchEffect
回調(diào)函數(shù)訂閱者,以及counter1
和counter2
這兩個(gè)Dep依賴。此時(shí)的響應(yīng)式模型如下圖:
從上圖可以看到此時(shí)只有X坐標(biāo)軸的Dep依賴,以及Y坐標(biāo)軸的Sub訂閱者,沒(méi)有一個(gè)Link節(jié)點(diǎn)。
我們接著來(lái)看看dep依賴的track
方法,核心代碼如下:
class Dep { // 指向Link鏈表的尾部節(jié)點(diǎn) subs: Link; track() { let link = new Link(activeSub, this); if (!activeSub.deps) { activeSub.deps = activeSub.depsTail = link; } else { link.prevDep = activeSub.depsTail; activeSub.depsTail!.nextDep = link; activeSub.depsTail = link; } addSub(link); } }
從上面的代碼可以看到,每執(zhí)行一次track
方法,也就是說(shuō)每次收集依賴,都會(huì)執(zhí)行new Link
去生成一個(gè)Link節(jié)點(diǎn)。
并且傳入兩個(gè)參數(shù),activeSub
為當(dāng)前active的訂閱者,在這里就是Sub1
(第一個(gè)watchEffect
)。第二個(gè)參數(shù)為this
,指向當(dāng)前的Dep依賴對(duì)象,也就是Dep1
(counter1
變量)。
先不看track
后面的代碼,我們來(lái)看看Link
這個(gè)class的代碼,核心代碼如下:
class Link { // 指向Link鏈表的后一個(gè)節(jié)點(diǎn)(X軸) nextDep: Link; // 指向Link鏈表的前一個(gè)節(jié)點(diǎn)(X軸) prevDep: Link; // 指向Link鏈表的下一個(gè)節(jié)點(diǎn)(Y軸) nextSub: Link; // 指向Link鏈表的上一個(gè)節(jié)點(diǎn)(Y軸) prevSub: Link; - constructor(public sub: Subscriber, public dep: Dep) { // ...省略 } }
細(xì)心的小伙伴可能發(fā)現(xiàn)了在Link
中沒(méi)有聲明sub
和dep
屬性,那么為什么前面我們會(huì)說(shuō)Link節(jié)點(diǎn)中的sub
和dep
屬性分別指向Sub訂閱者和Dep依賴呢?
因?yàn)樵赾onstructor構(gòu)造函數(shù)中使用了public
關(guān)鍵字,所以sub
和dep
就作為屬性暴露出來(lái)了。
執(zhí)行完let link = new Link(activeSub, this)
后,在響應(yīng)式系統(tǒng)模型中初始化出來(lái)第一個(gè)Link節(jié)點(diǎn),如下圖:
從上圖可以看到Link1
的sub
屬性指向Sub1
訂閱者,dep
屬性指向Dep1
依賴。
我們接著來(lái)看track
方法中剩下的代碼,如下:
class Dep { // 指向Link鏈表的尾部節(jié)點(diǎn) subs: Link; track() { let link = new Link(activeSub, this); if (!activeSub.deps) { activeSub.deps = activeSub.depsTail = link; } else { link.prevDep = activeSub.depsTail; activeSub.depsTail!.nextDep = link; activeSub.depsTail = link; } addSub(link); } }
先來(lái)看if (!activeSub.deps)
,activeSub
前面講過(guò)了是Sub1
。activeSub.deps
就是Sub1
的deps
屬性,也就是Sub1
隊(duì)列上的第一個(gè)Link。
從上圖中可以看到此時(shí)的Sub1
并沒(méi)有箭頭指向Link1
,所以if (!activeSub.deps)
為true,代碼會(huì)執(zhí)行
activeSub.deps = activeSub.depsTail = link;
deps
和depsTail
屬性分別指向Sub1
隊(duì)列的頭部和尾部,當(dāng)前隊(duì)列中只有Link1
這一個(gè)節(jié)點(diǎn),那么頭部和尾部當(dāng)然都指向Link1
。
執(zhí)行完這行代碼后響應(yīng)式模型圖就變成下面這樣的了,如下圖:
從上圖中可以看到Sub1
的隊(duì)列中只有Link1
這一個(gè)節(jié)點(diǎn),所以隊(duì)列的頭部和尾部都指向Link1
。
處理完Sub1
的隊(duì)列,但是Dep1
的隊(duì)列還沒(méi)處理,Dep1
的隊(duì)列是由addSub(link)
函數(shù)處理的。addSub
函數(shù)代碼如下:
function addSub(link: Link) { const currentTail = link.dep.subs; if (currentTail !== link) { link.prevSub = currentTail; if (currentTail) currentTail.nextSub = link; } link.dep.subs = link; }
由于Dep1
隊(duì)列中沒(méi)有Link節(jié)點(diǎn),所以此時(shí)在addSub
函數(shù)中主要是執(zhí)行第三塊代碼:link.dep.subs = link
。`
link.dep
是指向Dep1
,前面我們講過(guò)了Dep依賴的subs
屬性指向隊(duì)列的尾部。所以link.dep.subs = link
就是將Link1
指向Dep1
的隊(duì)列的尾部,執(zhí)行完這行代碼后響應(yīng)式模型圖就變成下面這樣的了,如下圖:
到這里對(duì)第一個(gè)響應(yīng)式變量counter1
進(jìn)行讀操作進(jìn)行的依賴收集就完了。
第一個(gè)watchEffect對(duì)counter2進(jìn)行讀操作
在第一個(gè)watchEffect中接著會(huì)對(duì)counter2
變量進(jìn)行讀操作。同樣會(huì)走到get
攔截中,然后執(zhí)行track
函數(shù),代碼如下:
class Dep { // 指向Link鏈表的尾部節(jié)點(diǎn) subs: Link; track() { let link = new Link(activeSub, this); if (!activeSub.deps) { activeSub.deps = activeSub.depsTail = link; } else { link.prevDep = activeSub.depsTail; activeSub.depsTail!.nextDep = link; activeSub.depsTail = link; } addSub(link); } }
同樣的會(huì)執(zhí)行一次new Link(activeSub, this)
,然后把新生成的Link2
的sub
和dep
屬性分別指向Sub1
和Dep2
。執(zhí)行后的響應(yīng)式模型圖如下圖:
從上面的圖中可以看到此時(shí)Sub1
的deps
屬性是指向Link1
的,所以這次代碼會(huì)走進(jìn)else
模塊中。else
部分代碼如下:
link.prevDep = activeSub.depsTail; activeSub.depsTail.nextDep = link; activeSub.depsTail = link;
activeSub.depsTail
指向Sub1
隊(duì)列尾部的Link,值是Link1
。所以執(zhí)行link.prevDep = activeSub.depsTail
就是將Link2
的prevDep
屬性指向Link1
。
同理activeSub.depsTail.nextDep = link
就是將Link1
的nextDep
屬性指向Link2
,執(zhí)行完這兩行代碼后Link1
和Link2
之間就建立關(guān)系了。如下圖:
從上圖中可以看到此時(shí)Link1
和Link2
之間就有箭頭連接,可以互相訪問(wèn)到對(duì)方。
最后就是執(zhí)行activeSub.depsTail = link
,這行代碼是將Sub1
隊(duì)列的尾部指向Link2
。執(zhí)行完這行代碼后模型圖如下:
Sub1
訂閱者的隊(duì)列就處理完了,接著就是處理Dep2
依賴的隊(duì)列。Dep2
的處理方式和Dep1
是一樣的,讓Dep2
隊(duì)列的隊(duì)尾指向Link2
,處理完了后模型圖如下:
到這里第一個(gè)watchEffect(也就是Sub1
)對(duì)其依賴的兩個(gè)響應(yīng)式變量counter1
(也就是Dep1
)和counter2
(也就是Dep2
),進(jìn)行依賴收集的過(guò)程就執(zhí)行完了。
第二個(gè)watchEffect對(duì)counter1進(jìn)行讀操作
接著我們來(lái)看第二個(gè)watchEffect
,同樣的還是會(huì)對(duì)counter1
進(jìn)行讀操作。然后觸發(fā)其get
攔截,接著執(zhí)行track
方法。回憶一下track
方法的代碼,如下:
class Dep { // 指向Link鏈表的尾部節(jié)點(diǎn) subs: Link; track() { let link = new Link(activeSub, this); if (!activeSub.deps) { activeSub.deps = activeSub.depsTail = link; } else { link.prevDep = activeSub.depsTail; activeSub.depsTail!.nextDep = link; activeSub.depsTail = link; } addSub(link); } }
這里還是會(huì)使用new Link(activeSub, this)
創(chuàng)建一個(gè)Link3
節(jié)點(diǎn),節(jié)點(diǎn)的sub
和dep
屬性分別指向Sub2
和Dep1
。如下圖:
同樣的Sub2
隊(duì)列上此時(shí)還沒(méi)任何值,所以if (!activeSub.deps)
為true,和之前一樣會(huì)去執(zhí)行activeSub.deps = activeSub.depsTail = link;
將Sub2
隊(duì)列的頭部和尾部都設(shè)置為Link3
。如下圖:
處理完Sub2
隊(duì)列后就應(yīng)該調(diào)用addSub
函數(shù)來(lái)處理Dep1
的隊(duì)列了,回憶一下addSub
函數(shù),代碼如下:
function addSub(link: Link) { const currentTail = link.dep.subs; if (currentTail !== link) { link.prevSub = currentTail; if (currentTail) currentTail.nextSub = link; } link.dep.subs = link; }
link.dep
指向Dep1
依賴,link.dep.subs
指向Dep1
依賴隊(duì)列的尾部。從前面的圖可以看到此時(shí)隊(duì)列的尾部是Link1
,所以currentTail
的值就是Link1
。
if (currentTail !== link)
也就是判斷Link1
和Link3
是否相等,很明顯不相等,就會(huì)走到if的里面去。
接著就是執(zhí)行link.prevSub = currentTail
,前面講過(guò)了此時(shí)link
就是Link3
,currentTail
就是Link1
。執(zhí)行這行代碼就是將Link3
的prevSub
屬性指向Link1
。
接著就是執(zhí)行currentTail.nextSub = link
,這行代碼是將Link1
的nextSub
指向Link3
。
執(zhí)行完上面這兩行代碼后Link1
和Link3
之間就建立聯(lián)系了,可以通過(guò)prevSub
和nextSub
屬性訪問(wèn)到對(duì)方。如下圖:
接著就是執(zhí)行link.dep.subs = link
,將Dep1
隊(duì)列的尾部指向Link3
,如下圖:
到這里第一個(gè)響應(yīng)式變量counter1進(jìn)行依賴收集就完成了。
第二個(gè)watchEffect對(duì)counter2進(jìn)行讀操作
在第二個(gè)watchEffect中接著會(huì)對(duì)counter2
變量進(jìn)行讀操作。同樣會(huì)走到get
攔截中,然后執(zhí)行track
函數(shù),代碼如下:
class Dep { // 指向Link鏈表的尾部節(jié)點(diǎn) subs: Link; track() { let link = new Link(activeSub, this); if (!activeSub.deps) { activeSub.deps = activeSub.depsTail = link; } else { link.prevDep = activeSub.depsTail; activeSub.depsTail!.nextDep = link; activeSub.depsTail = link; } addSub(link); } }
這里還是會(huì)使用new Link(activeSub, this)
創(chuàng)建一個(gè)Link4
節(jié)點(diǎn),節(jié)點(diǎn)的sub
和dep
屬性分別指向Sub2
和Dep2
。如下圖:
此時(shí)的activeSub
就是Sub2
,activeSub.deps
就是指向Sub2
隊(duì)列的頭部。所以此時(shí)頭部是指向Link3
,代碼會(huì)走到else模塊中。
在else中首先會(huì)執(zhí)行link.prevDep = activeSub.depsTail
,activeSub.depsTail
是指向Sub2
隊(duì)列的尾部,也就是Link3
。執(zhí)行完這行代碼后會(huì)將Link4
的prevDep
指向Link3
。
接著就是執(zhí)行activeSub.depsTail!.nextDep = link
,前面講過(guò)了activeSub.depsTail
是指向Link3
。執(zhí)行完這行代碼后會(huì)將Link3
的nextDep
屬性指向Link4
。
執(zhí)行完上面這兩行代碼后Link3
和Link4
之間就建立聯(lián)系了,可以通過(guò)nextDep
和prevDep
屬性訪問(wèn)到對(duì)方。如下圖:
接著就是執(zhí)行activeSub.depsTail = link
,將Sub2
隊(duì)列的尾部指向Link4
。如下圖:
接著就是執(zhí)行addSub
函數(shù)處理Dep2
的隊(duì)列,代碼如下:
function addSub(link: Link) { const currentTail = link.dep.subs; if (currentTail !== link) { link.prevSub = currentTail; if (currentTail) currentTail.nextSub = link; } link.dep.subs = link; }
link.dep
指向Dep2
依賴,link.dep.subs
指向Dep2
依賴隊(duì)列的尾部。從前面的圖可以看到此時(shí)隊(duì)列的尾部是Link2
,所以currentTail
的值就是Link2
。前面講過(guò)了此時(shí)link
就是Link4
,if (currentTail !== link)
也就是判斷Link2
和Link4
是否相等,很明顯不相等,就會(huì)走到if的里面去。
接著就是執(zhí)行link.prevSub = currentTail
,currentTail
就是Link2
。執(zhí)行這行代碼就是將Link4
的prevSub
屬性指向Link2
。
接著就是執(zhí)行currentTail.nextSub = link
,這行代碼是將Link2
的nextSub
指向Link4
。
執(zhí)行完上面這兩行代碼后Link2
和Link4
之間就建立聯(lián)系了,可以通過(guò)prevSub
和nextSub
屬性訪問(wèn)到對(duì)方。如下圖:
最后就是執(zhí)行link.dep.subs = link
將Dep2
隊(duì)列的尾部指向Link4
,如下圖:
至此整個(gè)依賴收集過(guò)程就完成了,最終就畫出了Vue新的響應(yīng)式模型。
依賴觸發(fā)
當(dāng)執(zhí)行counter1.value++
時(shí),就會(huì)被變量counter1
(也就是Dep1依賴
)的set函數(shù)攔截。
此時(shí)就可以通過(guò)Dep1
的subs
屬性指向隊(duì)列的尾部,也就是指向Link3
。
Link3
中可以直接通過(guò)sub
屬性訪問(wèn)到訂閱者Sub2
,也就是第二個(gè)watchEffect
,從而執(zhí)行第二個(gè)watchEffect
的回調(diào)函數(shù)。
接著就是使用Link的preSub
屬性從隊(duì)尾依次移動(dòng)到隊(duì)頭,從而觸發(fā)Dep1
隊(duì)列中的所有Sub訂閱者。
在這里就是使用preSub
屬性訪問(wèn)到Link1
(就到隊(duì)列的頭部啦),Link1
中可以直接通過(guò)sub
屬性訪問(wèn)到訂閱者Sub1
,也就是第一個(gè)watchEffect
,從而執(zhí)行第一個(gè)watchEffect
的回調(diào)函數(shù)。
總結(jié)
本文講了Vue新的響應(yīng)式模型,里面主要有三個(gè)角色:Dep依賴
、Sub訂閱者
、Link節(jié)點(diǎn)
。
Dep依賴
和Sub訂閱者
不再有直接的聯(lián)系,而是通過(guò)Link節(jié)點(diǎn)
作為橋梁。
依賴收集的過(guò)程中會(huì)構(gòu)建Dep依賴
的隊(duì)列,隊(duì)列是由Link節(jié)點(diǎn)
組成。以及構(gòu)建Sub訂閱者
的隊(duì)列,隊(duì)列同樣是由Link節(jié)點(diǎn)
組成。
依賴觸發(fā)時(shí)就可以通過(guò)Dep依賴
的隊(duì)列的隊(duì)尾出發(fā),Link節(jié)點(diǎn)
可以訪問(wèn)和觸發(fā)對(duì)應(yīng)的Sub訂閱者
。
然后依次從隊(duì)尾向隊(duì)頭移動(dòng),依次觸發(fā)隊(duì)列中每個(gè)Link節(jié)點(diǎn)
的Sub訂閱者
。
以上就是一文帶你搞懂Vue3.5的響應(yīng)式重構(gòu)的詳細(xì)內(nèi)容,更多關(guān)于Vue3.5響應(yīng)式重構(gòu)的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
vue關(guān)于重置表單數(shù)據(jù)出現(xiàn)undefined的解決
這篇文章主要介紹了vue關(guān)于重置表單數(shù)據(jù)出現(xiàn)undefined的解決方案,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-09-09vue-cli4創(chuàng)建項(xiàng)目導(dǎo)入Element-UI踩過(guò)的坑及解決
這篇文章主要介紹了vue-cli4創(chuàng)建項(xiàng)目導(dǎo)入Element-UI踩過(guò)的坑及解決,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2024-04-04Nuxt封裝@nuxtjs/axios請(qǐng)求后端數(shù)據(jù)方式
這篇文章主要介紹了Nuxt封裝@nuxtjs/axios請(qǐng)求后端數(shù)據(jù)方式,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2023-10-10vue 接口請(qǐng)求地址前綴本地開(kāi)發(fā)和線上開(kāi)發(fā)設(shè)置方式
這篇文章主要介紹了vue 接口請(qǐng)求地址前綴本地開(kāi)發(fā)和線上開(kāi)發(fā)設(shè)置方式,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2020-08-08Vue實(shí)現(xiàn)上拉加載下一頁(yè)效果的示例代碼
這篇文章主要為大家詳細(xì)介紹了如何利用Vue實(shí)現(xiàn)上拉加載下一頁(yè)效果,文中的示例代碼講解詳細(xì),對(duì)我們學(xué)習(xí)Vue有一定幫助,需要的可以參考一下2022-08-08使用Vue和ECharts創(chuàng)建交互式圖表的代碼示例
在現(xiàn)代 Web 應(yīng)用中,數(shù)據(jù)可視化是一個(gè)重要的組成部分,它不僅能夠幫助用戶更好地理解復(fù)雜的數(shù)據(jù),還能提升用戶體驗(yàn),本文給大家使用Vue和ECharts創(chuàng)建交互式圖表的示例,需要的朋友可以參考下2024-11-11