嘿,日安!

Day 16 - 效能處理:LinkPool

25th September 2025
Front-end
Vue3
IT鐵人賽2025
從零到一打造 Vue3 響應式系統
Last updated:26th September 2025
6 Minutes
1015 Words

昨天,我們完成了「依賴清理」機制,讓 effect 能夠正確處理動態變化的依賴關係。然而,這也帶來了一個新的效能問題:當依賴頻繁變化時,系統需要不斷地建立和刪除 Link 節點,每次建立依賴關係都會觸發記憶體分配,頻繁的分配/釋放會導致:

  • 垃圾回收壓力增大:GC 執行得越頻繁,就越可能造成應用程式的短暫卡頓。
  • 記憶體碎片化:頻繁處理和釋放小塊記憶體,可能導致記憶體空間中出現大量不連續的記憶體碎片。
  • 效能下降:記憶體管理本身的成本

我們可以透過物件池(Object Pool)的設計模式來解決這個問題。

Object Pool 設計模式

物件池(Object Pool)是一種設計模式,用於管理和重複使用物件,避免頻繁新增和刪除物件帶來的效能耗損。

與其在需要時新增、在用完時刪除,不如將可重複使用的物件統一管理起來,實現循環利用。 這個物件池就像一個「倉庫」,預先存放一批可以重複使用的物件。當需要物件時從池中取出,使用完畢後放回到池中,而不是刪除。

這樣可以達到:

  • 重複使用已分配的記憶體:避免了大量的記憶體分配操作
  • 減少垃圾回收次數:降低對主執行緒的干擾

LinkPool 採用單向鏈表結構,並且依照後進先出 (LIFO) 的原則 。主要是因為新增、刪除節點都只需要用到頭節點的操作,時間複雜度為 O(1),效率比較高。

我們接下來的執行步驟如下:

  • LinkPool 未使用
  • 移除 Link2 節點
  • 移除 Link1 節點
  • 復用在 linkPool 的節點

可以觀察一下他們的鏈表關係以及 LinkPool 的使用。

初始化

default

linkPool 池是空的,什麼都還沒跑。沒有回收節點可用。

移除 Link2

default

透過endTrack(sub) 判定有「尾段過期」→ 呼叫 clearTracking(Link2)

移除 Link1

default

透過endTrack(sub) 判定有「尾段過期」→ 呼叫 clearTracking(Link1)

加入 Link1

default

執行 link(dep, sub),這次 if (linkPool)true,走重用分支。

LinkPool 程式碼實作

system.ts
1
interface Dep {
2
subs: Link | undefined
3
subsTail: Link | undefined
4
}
5
6
interface Sub {
7
deps: Link | undefined
8
depsTail: Link | undefined
9
}
10
11
export interface Link {
12
sub: Sub
13
nextSub: Link
122 collapsed lines
14
prevSub: Link
15
dep: Dep
16
17
nextDep: Link | undefined
18
}
19
20
let linkPool: Link
21
22
export function link(dep, sub) {
23
24
const currentDep = sub.depsTail
25
const nextDep = currentDep === undefined ? sub.deps : currentDep.nextDep
26
if (nextDep && nextDep.dep === dep) {
27
sub.depsTail = nextDep
28
return
29
}
30
31
let newLink
32
33
/**
34
* 查看 linkPool 是否存在,如果存在,表示有復用節點
35
*/
36
37
if(linkPool){
38
newLink = linkPool
39
linkPool = linkPool.nextDep
40
newLink.nextDep = nextDep
41
newLink.dep = dep
42
newLink.sub = sub
43
}else{
44
/**
45
* 如果 linkPool 不存在,表示沒有復用節點,那就新建一個節點
46
*/
47
newLink = {
48
sub,
49
dep,
50
nextDep,
51
nextSub: undefined,
52
prevSub: undefined
53
}
54
}
55
56
if (dep.subsTail) {
57
dep.subsTail.nextSub = newLink
58
newLink.prevSub = dep.subsTail
59
dep.subsTail = newLink
60
} else {
61
dep.subs = newLink
62
dep.subsTail = newLink
63
}
64
65
if (sub.depsTail) {
66
sub.depsTail.nextDep = newLink
67
sub.depsTail = newLink
68
} else {
69
sub.deps = newLink
70
sub.depsTail = newLink
71
}
72
73
}
74
75
export function propagate(subs) {
76
let link = subs
77
let queuedEffect = []
78
79
while (link) {
80
queuedEffect.push(link.sub)
81
link = link.nextSub
82
}
83
84
queuedEffect.forEach(effect => effect.notify())
85
}
86
87
export function startTrack(sub) {
88
sub.depsTail = undefined
89
}
90
91
export function endTrack(sub) {
92
const depsTail = sub.depsTail
93
94
if (depsTail) {
95
if (depsTail.nextDep) {
96
clearTracking(depsTail.nextDep)
97
depsTail.nextDep = undefined
98
}
99
} else if (sub.deps) {
100
clearTracking(sub.deps)
101
sub.deps = undefined
102
}
103
104
}
105
106
function clearTracking(link: Link) {
107
while (link) {
108
const { prevSub, nextSub, dep, nextDep } = link
109
110
if (prevSub) {
111
prevSub.nextSub = nextSub
112
link.nextSub = undefined
113
} else {
114
dep.subs = nextSub
115
}
116
117
if (nextSub) {
118
nextSub.prevSub = prevSub
119
link.prevSub = undefined
120
} else {
121
dep.subsTail = prevSub
122
}
123
124
link.dep = undefined
125
link.sub = undefined
126
127
/**
128
* 把不要的節點放回 linkPool 去復用
129
*/
130
link.nextDep = linkPool
131
linkPool = link
132
133
link = nextDep
134
}
135
}

透過對 link 和 clearTracking 函式的修改,我們完成了 LinkPool 機制。這看起來是一個很小的修改,但實際上是對響應式系統底層的重要效能優化。Link 節點的生命週期從「用完後刪除」變成了「循環再生」,從根本上解決因動態依賴而產生的頻繁記憶體分配與回收問題。

Article title:Day 16 - 效能處理:LinkPool
Article author:日安
Release time:25th September 2025