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.value21 console.count('effect')22 })23
24 btn.onclick = () => {25 flag.value = !flag.value26 }27 </script>28 </body>29</html>
看起來不太妙,目前 effect 出現按鈕指數觸發,這樣一定是不行。
我們預期每次點擊按鈕,effect
只會執行一次。但實際情況看起來不太妙。
從 console.count
的結果可以看到,effect
的執行次數隨著點擊呈現指數級增長。
我們來了解一下問題癥結點。
建立 Link 節點問題癥結點
執行步驟圖解
初始化頁面
頁面載入時,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 節點
- 導致鏈表無限增長
因此導致每次點擊按鈕,鏈表上的每一個 Link 都會建立新的 link,並且重複執行,造成指數級增長現象。
因為下個篇幅比較長就先講到這,大家要先理解問題的癥結點在哪,明天實作解決方案,才會知道為什麼要這樣做。