
我們的響應式系統經過前幾天的努力,已經做得差不多,感覺可以加上一下 DOM 的互動,來進行簡單的測試。
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<title>Title</title>
<style>
body {
padding: 150px;
}
</style>
</head>
<body>
<button id="btn">按鈕</button>
<script type="module">
import { ref, effect } from '../dist/reactivity.esm.js'
const flag = ref(true)
effect(() => {
flag.value
console.count('effect')
})
btn.onclick = () => {
flag.value = !flag.value
}
</script>
</body>
</html>

看起來不太妙,目前 effect 出現按鈕指數觸發,這樣一定是不行。
我們預期每次點擊按鈕,effect 只會執行一次。但實際情況看起來不太妙。
從 console.count 的結果可以看到,effect 的執行次數隨著點擊呈現指數級增長。
我們來了解一下問題癥結點。

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

當按鈕第一次被點擊,flag.value 從 true 變為 false,觸發了 setter。
setter 內的 propagate 函式開始遍歷 flag 的依賴鏈表。
propagate 執行 link1 中儲存的 effect.run()。
effect 函式重新執行,又讀取 flag.value。觸發了 getter。
此時問題出現了:執行完畢後,flag 的依賴鏈表結構如圖,現在有兩個節點 (link1, link2),但它們都指向同一個 effect,他們分別儲存 activeSub。
執行結束後的鏈表:


當按鈕又被點擊,flag.value 從 false 變為 true,再次觸發 setter。
propagate 開始遍歷依賴鏈表。但這一次,鏈表上有兩個節點 (link1 和 link2)。
propagate 先執行 link1 中的 effect.run()。effect 內部讀取 flag.value,再次觸發依賴收集,建立了一個新的 link3 節點加到鏈表尾部。
propagate 接著執行 link2 中的 effect.run()。effect 內部又一次讀取 flag.value,觸發依賴收集,又建立了一個新的 link4 節點並加到鏈表尾部。
執行結束後的鏈表:

我們可以發現在觸發更新的時候,鏈表上的所有節點全部都會分別建立一個新的節點,因此發生了指數觸發 effect 的情況。
每次 Effect 重新執行時:
因此導致每次點擊按鈕,鏈表上的每一個 Link 都會建立新的 link,並且重複執行,造成指數級增長現象。
因為下個篇幅比較長就先講到這,大家要先理解問題的癥結點在哪,明天實作解決方案,才會知道為什麼要這樣做。
同步更新《嘿,日安!》技術部落格