Vue响应式系统不依赖JS执行栈,而是通过activeEffect栈实现嵌套依赖追踪,并由调度队列控制effect执行时机,每个effect独立生命周期绑定cleanup逻辑,需防范依赖污染而非栈溢出。

Vue 响应式系统中没有传统意义上的“运行栈”(如 JavaScript 执行上下文栈)来维护副作用函数的调用顺序,它维护的是依赖关系栈和执行调度队列——核心目标不是记录“谁调用了谁”,而是确保“谁该在数据变化时被重新执行”。
activeEffect 栈:用于嵌套追踪依赖
当一个 effect 执行过程中又读取了另一个响应式对象的属性(比如 computed 或嵌套 effect),Vue 需区分当前正在收集依赖的是哪一个 effect。这时靠 activeEffect 栈(实际是单个变量 + 临时保存/恢复机制)实现:
- 每次进入 effect 函数前,将当前 effect 赋值给全局
activeEffect - 若 effect 内部触发了另一个响应式读取(例如访问了 computed 的 getter),而该 getter 又执行了新的 effect,则旧的
activeEffect会被暂存,新 effect 接管 - 新 effect 执行完后,恢复上一层的
activeEffect,保证依赖正确归属
这本质上是一种深度优先、单线程递归式依赖收集栈,不是运行时调用栈,也不保留历史帧,只服务于“此刻哪个 effect 正在读取数据”这一目的。
effect 执行不入 JS 调用栈,而是进调度队列
响应式更新触发的 effect 重执行,不会同步压入 JavaScript 执行栈,而是由调度器(scheduler)统一管理:
基于三引擎设计,从微信文章、新闻和博客网页提取干净内容,支持标题作者日期元数据,多格式和批量处理。
立即学习“前端免费学习笔记(深入)”;
- 默认 scheduler 把 effect 推入微任务队列(如
Promise.then或queueMicrotask) - 多个数据变更可能触发同一个 effect 多次,但队列会自动去重,最终只执行一次
- 组件更新相关的 effect(如渲染函数)会被合并为一个 job,避免重复 DOM 操作
这意味着你不会在浏览器调试器里看到 effect 函数层层嵌套出现在 Call Stack 中;它的“执行时机”由队列控制,而非调用链决定。
cleanup 与 effect 实例生命周期绑定
每个 effect 实例(effect 返回的清理函数、内部状态等)独立存在,不依赖栈结构维持:
- effect 执行前,若已存在同源旧 effect(如 watch 重新注册),会先调用其 cleanup 函数
- effect 函数可返回 cleanup 回调,在下一次自身重新运行前自动触发(如清除定时器、取消请求)
- 这个过程靠 effect 实例自身的
onStop钩子或手动注册的清理逻辑完成,与栈无关
没有“栈泄漏”,但有依赖污染风险
真正需要警惕的不是栈溢出,而是依赖未正确隔离:
- 若在条件分支中读取响应式数据(如
if (state.flag) console.log(state.msg)),flag 变化时 msg 的变化不会触发 effect —— 因为读取未发生,无依赖 - 异步回调中访问响应式数据,若回调执行时
activeEffect已退出(如 effect 已结束),则该读取不会被收集,导致丢失响应 - 解决方式:用
watch显式监听、或确保异步逻辑包裹在effect/watchEffect内部

















