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