自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

看不懂來打我!讓性能提升56%的Vue3.5響應(yīng)式重構(gòu)

開發(fā) 前端
這篇文章講了Vue新的響應(yīng)式模型,里面主要有三個(gè)角色:Dep依賴、Sub訂閱者、Link節(jié)點(diǎn)。Dep依賴和Sub訂閱者?不再有直接的聯(lián)系,而是通過Link節(jié)點(diǎn)作為橋梁。

前言

在Vue3.5版本中最大的改動(dòng)就是響應(yīng)式重構(gòu),重構(gòu)后性能竟然炸裂的提升了56%。之所以重構(gòu)后的響應(yīng)式性能提升幅度有這么大,主要還是歸功于:雙向鏈表和版本計(jì)數(shù)。這篇文章我們來講講使用雙向鏈表后,Vue內(nèi)部是如何實(shí)現(xiàn)依賴收集和依賴觸發(fā)的。

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)聽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)聽了多個(gè)ref變量。也就是說,一個(gè)Sub訂閱者(也就是一個(gè)watchEffect)可以訂閱多個(gè)依賴。

ref響應(yīng)式變量counter1被多個(gè)watchEffect給監(jiān)聽。也就是說,一個(gè)Dep依賴(也就是counter1變量)可以被多個(gè)訂閱者給訂閱。

Sub訂閱者和Dep依賴他們兩的關(guān)系是多對(duì)多的關(guān)系?。?!

圖片圖片

上面這個(gè)就是以前的響應(yīng)式模型。

新的響應(yīng)式模型

在Vue3.5版本新的響應(yīng)式中,Sub訂閱者和Dep依賴之間不再有直接的聯(lián)系,而是新增了一個(gè)Link作為橋梁。Sub訂閱者通過Link訪問到Dep依賴,同理Dep依賴也是通過Link訪問到Sub訂閱者。如下圖:

圖片圖片

把上面這個(gè)圖看懂了,你就能理解Vue新的響應(yīng)式系統(tǒng)啦?,F(xiàn)在你直接看這個(gè)圖有可能看不懂,沒關(guān)系,等我講完后你就能看懂了。

首先從上圖中可以看到Sub訂閱者和Dep依賴之間沒有任何直接的連接關(guān)系了,也就是說Sub訂閱者不能直接訪問到Dep依賴,Dep依賴也不能直接訪問Sub訂閱者。

Dep依賴我們可以看作是X軸,Sub訂閱者可以看作是Y軸,這些Link就是坐標(biāo)軸上面的坐標(biāo)。

Vue響應(yīng)式系統(tǒng)的核心還是沒有變,只是多了一個(gè)Link,依然還是以前的那一套依賴收集和依賴觸發(fā)的流程。

在依賴收集的過程中就會(huì)畫出上面這個(gè)圖,這個(gè)不要急,我接下來會(huì)仔細(xì)去講圖是如何畫出來的。

那么依賴觸發(fā)的時(shí)候又是如何利用上面這種圖從而實(shí)現(xiàn)觸發(fā)依賴的呢?我們來看個(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。

前面我們講過了這個(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。

前面我們講過了這個(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í)再來回顧一下我們前面畫的新的響應(yīng)式模型圖,如下圖:

圖片圖片

我們從這張圖來總結(jié)一下依賴觸發(fā)的的規(guī)則:

響應(yīng)式變量Dep1改變后,首先會(huì)指向Y軸(Sub訂閱者)的隊(duì)尾的Link節(jié)點(diǎn)。然后從Link節(jié)點(diǎn)可以直接訪問到Sub訂閱者,訪問到訂閱者后就可以觸發(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é)有疑問如果是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ā)原理,接下來我們會(huì)去講依賴收集過程中是如何將上面的模型圖畫出來的。

Dep、Sub和Link

在講Vue3.5版本依賴收集之前,我們先來了解一下新的響應(yīng)式系統(tǒng)中主要的三個(gè)角色:Dep依賴、Sub訂閱者、Link節(jié)點(diǎn)。

這三個(gè)角色其實(shí)都是class類,依賴收集和依賴觸發(fā)的過程中實(shí)際就是在操作這些類new出來的的對(duì)象。

我們接下來看看這些類中有哪些屬性和方法,其實(shí)在前面的響應(yīng)式模型圖中我們已經(jīng)使用箭頭標(biāo)明了這些類上面的屬性。

Dep依賴

簡化后的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訂閱者

簡化后的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來說,執(zhí)行notify函數(shù)后就會(huì)執(zhí)行watchEffect的回調(diào)函數(shù)。

Link節(jié)點(diǎ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
}

前面我們講過了新的響應(yīng)式模型中Dep依賴和Sub訂閱者之間不會(huì)再有直接的關(guān)聯(lián),而是通過Link作為橋梁。

那么作為橋梁的Link節(jié)點(diǎn)肯定需要有兩個(gè)屬性能夠讓他直接訪問到Dep依賴和Sub訂閱者,也就是sub和dep屬性。

其中的sub屬性是指向Sub訂閱者,dep屬性是指向Dep依賴。

圖片圖片

我們知道Link是坐標(biāo)軸的點(diǎn),那這個(gè)點(diǎn)肯定就會(huì)有上、下、左、右四個(gè)方向。

比如對(duì)于Link1可以使用nextDep屬性來訪問后面這個(gè)節(jié)點(diǎn)Link2,Link2可以使用prevDep屬性來訪問前面這個(gè)節(jié)點(diǎn)Link1。

請(qǐng)注意,這里名字雖然叫nextDep和prevDep,但是他們指向的卻是Link節(jié)點(diǎn)。然后通過這個(gè)Link節(jié)點(diǎn)的dep屬性,就可以訪問到后一個(gè)Dep依賴或者前一個(gè)Dep依賴。

同理對(duì)于Link1可以使用nextSub訪問后面這個(gè)節(jié)點(diǎn)Link3,Link3可以使用prevSub訪問前面這個(gè)節(jié)點(diǎn)Link1。

同樣的這里名字雖然叫nextSub和prevSub,但是他們指向的卻是Link節(jié)點(diǎn)。然后通過這個(gè)Link節(jié)點(diǎn)的sub屬性,就可以訪問到下一個(gè)Sub訂閱者或者上一個(gè)Sub訂閱者。

如何收集依賴

搞清楚了新的響應(yīng)式模型中的三個(gè)角色:Dep依賴、Sub訂閱者、Link節(jié)點(diǎn),我們現(xiàn)在就可以開始搞清楚新的響應(yīng)式模型是如何收集依賴的。

接下來我將會(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)式變量有g(shù)et和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訂閱者,沒有一個(gè)Link節(jié)點(diǎn)。

我們接著來看看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方法,也就是說每次收集依賴,都會(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后面的代碼,我們來看看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中沒有聲明sub和dep屬性,那么為什么前面我們會(huì)說Link節(jié)點(diǎn)中的sub和dep屬性分別指向Sub訂閱者和Dep依賴呢?

因?yàn)樵赾onstructor構(gòu)造函數(shù)中使用了public關(guān)鍵字,所以sub和dep就作為屬性暴露出來了。

執(zhí)行完let link = new Link(activeSub, this)后,在響應(yīng)式系統(tǒng)模型中初始化出來第一個(gè)Link節(jié)點(diǎn),如下圖:

圖片圖片

從上圖可以看到Link1的sub屬性指向Sub1訂閱者,dep屬性指向Dep1依賴。

我們接著來看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);
}
}

先來看if (!activeSub.deps),activeSub前面講過了是Sub1。activeSub.deps就是Sub1的deps屬性,也就是Sub1隊(duì)列上的第一個(gè)Link。

從上圖中可以看到此時(shí)的Sub1并沒有箭頭指向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ì)列還沒處理,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ì)列中沒有Link節(jié)點(diǎn),所以此時(shí)在addSub函數(shù)中主要是執(zhí)行第三塊代碼:link.dep.subs = link。`

link.dep是指向Dep1,前面我們講過了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í)行l(wèi)ink.prevDep = activeSub.depsTail就是將Link2的prevDep屬性指向Link1。

同理activeSub.depsTail.nextDep = link就是將Link1的nextDep屬性指向Link2,執(zhí)行完這兩行代碼后Link1和Link2之間就建立關(guān)系了。如下圖:

圖片圖片

從上圖中可以看到此時(shí)Link1和Link2之間就有箭頭連接,可以互相訪問到對(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)行依賴收集的過程就執(zhí)行完了。

第二個(gè)watchEffect對(duì)counter1進(jìn)行讀操作

接著我們來看第二個(gè)watchEffect,同樣的還是會(huì)對(duì)counter1進(jìn)行讀操作。然后觸發(fā)其get攔截,接著執(zhí)行track方法?;貞浺幌聇rack方法的代碼,如下:

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í)還沒任何值,所以if (!activeSub.deps)為true,和之前一樣會(huì)去執(zhí)行activeSub.deps = activeSub.depsTail = link;將Sub2隊(duì)列的頭部和尾部都設(shè)置為Link3。如下圖:

圖片圖片

處理完Sub2隊(duì)列后就應(yīng)該調(diào)用addSub函數(shù)來處理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í)行l(wèi)ink.prevSub = currentTail,前面講過了此時(shí)link就是Link3,currentTail就是Link1。執(zhí)行這行代碼就是將Link3的prevSub屬性指向Link1。

接著就是執(zhí)行currentTail.nextSub = link,這行代碼是將Link1的nextSub指向Link3。

執(zhí)行完上面這兩行代碼后Link1和Link3之間就建立聯(lián)系了,可以通過prevSub和nextSub屬性訪問到對(duì)方。如下圖:

圖片圖片

接著就是執(zhí)行l(wèi)ink.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í)行l(wèi)ink.prevDep = activeSub.depsTail,activeSub.depsTail是指向Sub2隊(duì)列的尾部,也就是Link3。執(zhí)行完這行代碼后會(huì)將Link4的prevDep指向Link3。

接著就是執(zhí)行activeSub.depsTail!.nextDep = link,前面講過了activeSub.depsTail是指向Link3。執(zhí)行完這行代碼后會(huì)將Link3的nextDep屬性指向Link4。

執(zhí)行完上面這兩行代碼后Link3和Link4之間就建立聯(lián)系了,可以通過nextDep和prevDep屬性訪問到對(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。前面講過了此時(shí)link就是Link4,if (currentTail !== link)也就是判斷Link2和Link4是否相等,很明顯不相等,就會(huì)走到if的里面去。

接著就是執(zhí)行l(wèi)ink.prevSub = currentTail,currentTail就是Link2。執(zhí)行這行代碼就是將Link4的prevSub屬性指向Link2。

接著就是執(zhí)行currentTail.nextSub = link,這行代碼是將Link2的nextSub指向Link4。

執(zhí)行完上面這兩行代碼后Link2和Link4之間就建立聯(lián)系了,可以通過prevSub和nextSub屬性訪問到對(duì)方。如下圖:

圖片圖片

最后就是執(zhí)行l(wèi)ink.dep.subs = link將Dep2隊(duì)列的尾部指向Link4,如下圖:

圖片圖片

至此整個(gè)依賴收集過程就完成了,最終就畫出了Vue新的響應(yīng)式模型。

依賴觸發(fā)

當(dāng)執(zhí)行counter1.value++時(shí),就會(huì)被變量counter1(也就是Dep1依賴)的set函數(shù)攔截。

此時(shí)就可以通過Dep1的subs屬性指向隊(duì)列的尾部,也就是指向Link3。

Link3中可以直接通過sub屬性訪問到訂閱者Sub2,也就是第二個(gè)watchEffect,從而執(zhí)行第二個(gè)watchEffect的回調(diào)函數(shù)。

接著就是使用Link的preSub屬性從隊(duì)尾依次移動(dòng)到隊(duì)頭,從而觸發(fā)Dep1隊(duì)列中的所有Sub訂閱者。

在這里就是使用preSub屬性訪問到Link1(就到隊(duì)列的頭部啦),Link1中可以直接通過sub屬性訪問到訂閱者Sub1,也就是第一個(gè)watchEffect,從而執(zhí)行第一個(gè)watchEffect的回調(diào)函數(shù)。

總結(jié)

這篇文章講了Vue新的響應(yīng)式模型,里面主要有三個(gè)角色:Dep依賴、Sub訂閱者、Link節(jié)點(diǎn)。

Dep依賴和Sub訂閱者不再有直接的聯(lián)系,而是通過Link節(jié)點(diǎn)作為橋梁。

依賴收集的過程中會(huì)構(gòu)建Dep依賴的隊(duì)列,隊(duì)列是由Link節(jié)點(diǎn)組成。以及構(gòu)建Sub訂閱者的隊(duì)列,隊(duì)列同樣是由Link節(jié)點(diǎn)組成。

依賴觸發(fā)時(shí)就可以通過Dep依賴的隊(duì)列的隊(duì)尾出發(fā),Link節(jié)點(diǎn)可以訪問和觸發(fā)對(duì)應(yīng)的Sub訂閱者。

然后依次從隊(duì)尾向隊(duì)頭移動(dòng),依次觸發(fā)隊(duì)列中每個(gè)Link節(jié)點(diǎn)的Sub訂閱者。

責(zé)任編輯:武曉燕 來源: 前端歐陽
相關(guān)推薦

2024-11-06 10:47:53

2024-11-13 09:57:22

2017-06-16 09:22:22

數(shù)據(jù)結(jié)構(gòu)算法鏈表

2022-07-26 14:38:08

JavaScriptWeb安全自動(dòng)化

2021-12-09 11:59:49

JavaScript前端提案

2022-12-27 10:02:38

MVCC機(jī)制Innodb

2019-12-09 08:29:26

Netty架構(gòu)系統(tǒng)

2024-09-04 11:42:17

Vue3.5源碼API

2020-03-30 16:45:06

代碼看不懂

2022-06-16 14:07:26

Java代碼代碼review

2022-12-12 07:40:36

服務(wù)器項(xiàng)目Serverless

2021-09-06 07:58:47

鏈表數(shù)據(jù)結(jié)構(gòu)

2022-01-05 09:40:03

DIff算法前端

2023-06-30 08:01:04

Reactuse關(guān)鍵詞

2019-11-12 10:38:59

DevOps程序員軟件開發(fā)工程師

2025-02-17 08:58:06

2022-02-07 09:05:00

GitHub功能AI

2014-03-12 09:25:33

產(chǎn)品經(jīng)理Startup

2024-09-02 08:48:45

2017-09-19 15:45:39

點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號(hào)