
昨天,我们完成了"依赖清理"机制,让 effect
能够正确处理动态变化的依赖关系。然而,这也带来了一个新的性能问题:当依赖频繁变化时,系统需要不断地创建和销毁 Link
节点。每次建立依赖关系都会触发内存分配,频繁的分配/释放会导致:
- 垃圾回收 (GC) 压力增大:GC 执行得越频繁,就越可能造成应用程序的短暂卡顿。
- 内存碎片化:频繁处理和释放小块内存,可能导致内存空间中出现大量不连续的内存碎片。
- 性能下降:内存管理本身的开销。
我们可以通过对象池 (Object Pool) 的设计模式来解决这个问题。
Object Pool 设计模式
对象池 (Object Pool) 是一种设计模式,用于管理和复用对象,以避免频繁创建和销毁对象带来的性能损耗。
与其在需要时创建、在用完时销毁,不如将可复用的对象统一管理起来,实现循环利用。 这个对象池就像一个"仓库",预先存放一批可以重复使用的对象。当需要对象时从池中取出,使用完毕后放回到池中,而不是销毁它。
这样可以达到:
- 复用已分配的内存:避免了大量的内存分配操作。
- 减少垃圾回收次数:降低对主线程的干扰。
Link Pool
LinkPool
采用单向链表结构,并且依照后进先出 (LIFO) 的原则。主要是因为入池、出池都只需要对头节点进行操作,时间复杂度为 O(1),效率很高。
Link Pool 生命周期
我们接下来的执行步骤如下:
LinkPool
未使用- 移除
Link2
节点 - 移除
Link1
节点 - 复用
linkPool
中的节点
可以观察一下它们的链表关系以及 LinkPool
的使用情况。
初始化
linkPool
池是空的,什么都还没运行,没有可回收的节点。
移除 Link2

通过 endTrack(sub)
判定有"尾段过期"→ 调用 clearTracking(Link2)
,Link2
被回收到池中。
移除 Link1

通过 endTrack(sub)
再次判定有"尾段过期"→ 调用 clearTracking(Link1)
,Link1
被回收到池中,并排在 Link2
前面。
复用 Link1

执行 link(dep, sub)
,这次 if (linkPool)
为 true ,走复用分支,从池中取出 Link1
进行复用。
LinkPool 代码实现
TypeScript
// system.ts
interface Dep {
subs: Link | undefined
subsTail: Link | undefined
}
interface Sub {
deps: Link | undefined
depsTail: Link | undefined
}
export interface Link {
sub: Sub
nextSub: Link
prevSub: Link
dep: Dep
nextDep: Link | undefined
}
let linkPool: Link
export function link(dep, sub) {
const currentDep = sub.depsTail
const nextDep = currentDep === undefined ? sub.deps : currentDep.nextDep
if (nextDep && nextDep.dep === dep) {
sub.depsTail = nextDep
return
}
let newLink: Link
/**
* 查看 linkPool 是否存在,如果存在,表示有可复用的节点
*/
if (linkPool) {
newLink = linkPool
linkPool = linkPool.nextDep // 池指针后移
newLink.nextDep = nextDep
newLink.dep = dep
newLink.sub = sub
} else {
/**
* 如果 linkPool 不存在,表示没有可复用的节点,那就创建一个新节点
*/
newLink = {
sub,
dep,
nextDep,
nextSub: undefined,
prevSub: undefined
}
}
if (dep.subsTail) {
dep.subsTail.nextSub = newLink
newLink.prevSub = dep.subsTail
dep.subsTail = newLink
} else {
dep.subs = newLink
dep.subsTail = newLink
}
if (sub.depsTail) {
sub.depsTail.nextDep = newLink
sub.depsTail = newLink
} else {
sub.deps = newLink
sub.depsTail = newLink
}
}
export function propagate(subs) {
// ... (不变)
}
export function startTrack(sub) {
// ... (不变)
}
export function endTrack(sub) {
// ... (不变)
}
function clearTracking(link: Link) {
while (link) {
const { prevSub, nextSub, dep, nextDep } = link
if (prevSub) {
prevSub.nextSub = nextSub
link.nextSub = undefined
} else {
dep.subs = nextSub
}
if (nextSub) {
nextSub.prevSub = prevSub
link.prevSub = undefined
} else {
dep.subsTail = prevSub
}
link.dep = undefined
link.sub = undefined
/**
* 把不再需要的节点放回 linkPool 中,以备复用
*/
link.nextDep = linkPool
linkPool = link
link = nextDep
}
}
通过对 link
和 clearTracking
函数的修改,我们完成了 LinkPool
机制。这看起来是一个很小的修改,但实际上是对响应式系统底层的重要性能优化。Link
节点的生命周期从"用完即毁"变成了"循环再生",从根本上解决了因动态依赖而产生的频繁内存分配与回收问题。
想了解更多 Vue 的相关知识,抖音、B站搜索我师父「远方os」,一起跟日安当同学。