2025.09.14
防重入锁:阻止无限循环依赖的机制
💥 问题溯源:为什么会产生无限循环?
在响应式系统中,一个经典的性能陷阱是:一个 effect 在单次执行期间同时完成了对同一个响应式数据的“读取”和“写入”操作,这种操作模式会立即触发一个死循环:
- 读取:
effect读取ref.value, 触发Track。 当前的effecgt被登记到了ref的订阅者链表中 - 写入:
effect立即修改ref.value, 派发通知(Trigger/Porpagate) - 循环:
ref的订阅链表中有这个effect, 便重新运行 - 读取:
effect重新运行, 从 1 开始重复, 导致 栈溢出
🚨 一段会导致系统崩溃的代码
<script type="module">
import {ref, effect} from '../dist/reactivity.esm.js'
const count = ref(0)
effect(() => {
// ❗ count.value++ 隐含了:读取 count.value,然后写入 count.value + 1
console.log(count.value++)
})
</script>

❗ 根本原因: 在派发通知时,系统没有判断目标 effect 是否正在运行中。
🛡️ 解决方案设计
解决无限循环的核心思路是引入一个防重入(Re-entry Guard) 机制,即在 effect 运行时为其加上一把“执行锁”。
tracking标记: 在reactiveEffect类中增加一个标识(tracking), 来识别effect是否在处于 正在运行 的状态- 加锁与解锁:
- 在
effect执行前(startTrack), 开锁(tracking = true) - 在
effect执行后(endTrack), 关锁(tracking = false)
- 在
- 派发通知时的检查: 在派发通知(
propagate) 阶段, 只有未加锁(!sub.tracking) 的effect才被加入到调度队列
核心代码
ReactiveEffect类// effect.ts export class ReactiveEffect { // ... (其他属性) tracking = false // 🚀 加锁标志: true 表示 effect 正在运行中 }- 在
effect周期内管理锁的状态(startTrackendTrack)// system.ts export function startTrack(sub: ReactiveEffect) { sub.tracking = true // 开锁 sub.depsTail = undefined } export function endTrack(sub: ReactiveEffect) { // ... (依赖清理逻辑) sub.tracking = false // 解锁 } - 派发通知
propagate
// system.ts
export function propagate(subs: Link) {
let linkNode = subs
const queuedEffect = [] // 用于收集待调度的 effect
while (linkNode) {
const sub = linkNode.sub
// 🛡️ 核心检查:只有不在执行中的 effect 才会被调度
if (!sub.tracking) {
queuedEffect.push(sub)
}
linkNode = linkNode.nextSub
}
// 统一执行调度
queuedEffect.forEach(effect => effect.notify())
}