嘿,日安!

Day 10 - 為何 Effect 會被指數級觸發?

19th September 2025
Front-end
Vue3
IT鐵人賽2025
從零到一打造 Vue3 響應式系統
Last updated:19th September 2025
4 Minutes
713 Words

DOM 互動

我們的響應式系統經過前幾天的努力,已經做得差不多,感覺可以加上一下 DOM 的互動,來進行簡單的測試。

1
<!doctype html>
2
<html lang="en">
3
<head>
4
<meta charset="UTF-8" />
5
<title>Title</title>
6
<style>
7
body {
8
padding: 150px;
9
}
10
</style>
11
</head>
12
<body>
13
<button id="btn">按鈕</button>
14
<script type="module">
15
import { ref, effect } from '../dist/reactivity.esm.js'
14 collapsed lines
16
17
const flag = ref(true)
18
19
effect(() => {
20
flag.value
21
console.count('effect')
22
})
23
24
btn.onclick = () => {
25
flag.value = !flag.value
26
}
27
</script>
28
</body>
29
</html>

default

看起來不太妙,目前 effect 出現按鈕指數觸發,這樣一定是不行。 我們預期每次點擊按鈕,effect 只會執行一次。但實際情況看起來不太妙。

console.count 的結果可以看到,effect 的執行次數隨著點擊呈現指數級增長。

我們來了解一下問題癥結點。

建立 Link 節點問題癥結點

執行步驟圖解

初始化頁面

default

頁面載入時,effect 執行一次。在執行過程中,讀取了 flag.value,觸發 getter 進行依賴收集。 系統會建立一個 link1 節點,將 effectflag 關聯起來。到這裡都符合預期。

點擊第一次按鈕

default

當按鈕第一次被點擊,flag.valuetrue 變為 false,觸發了 setter。 setter 內的 propagate 函式開始遍歷 flag 的依賴鏈表。

propagate 執行 link1 中儲存的 effect.run()

effect 函式重新執行,又讀取 flag.value。觸發了 getter。

此時問題出現了:執行完畢後,flag 的依賴鏈表結構如圖,現在有兩個節點 (link1, link2),但它們都指向同一個 effect,他們分別儲存 activeSub

執行結束後的鏈表:

default

點擊第二次按鈕

default

當按鈕又被點擊,flag.valuefalse 變為 true,再次觸發 setter。

propagate 開始遍歷依賴鏈表。但這一次,鏈表上有兩個節點 (link1 和 link2)。

propagate 先執行 link1 中的 effect.run()。effect 內部讀取 flag.value,再次觸發依賴收集,建立了一個新的 link3 節點加到鏈表尾部。

propagate 接著執行 link2 中的 effect.run()。effect 內部又一次讀取 flag.value,觸發依賴收集,又建立了一個新的 link4 節點並加到鏈表尾部。

執行結束後的鏈表:

default

執行完成的鏈表結構

我們可以發現在觸發更新的時候,鏈表上的所有節點全部都會分別建立一個新的節點,因此發生了指數觸發 effect 的情況。

關鍵問題點

每次 Effect 重新執行時:

  1. 沒有檢查是否已經建立過 Link
  2. 盲目創建新的 Link 節點
  3. 導致鏈表無限增長

因此導致每次點擊按鈕,鏈表上的每一個 Link 都會建立新的 link,並且重複執行,造成指數級增長現象。

因為下個篇幅比較長就先講到這,大家要先理解問題的癥結點在哪,明天實作解決方案,才會知道為什麼要這樣做。

Article title:Day 10 - 為何 Effect 會被指數級觸發?
Article author:日安
Release time:19th September 2025