从 Vue 2.x 源码角度分析将组件变量 a 从空值修改为 1 的完整调用栈如下:
1. 组件初始化阶段
在组件创建时,Vue 会初始化响应式数据:
// 调用栈:
Vue.prototype._init (init.js)
└── initState (state.js)
└── initData (state.js)
└── observe (observer/index.js)
└── new Observer (observer/index.js)
└── walk (observer/index.js)
└── defineReactive (observer/index.js) // 为属性 `a` 创建响应式
关键步骤:
defineReactive为a创建getter/setter:- 初始化
dep实例(依赖收集器)。 - 通过
Object.defineProperty重写a的访问器:Object.defineProperty(obj, key, { get() { /* 依赖收集 */ }, set(newVal) { /* 触发更新 */ } })
- 初始化
2. 修改 a 的值
执行 this.a = 1 时触发 setter:
// 调用栈:
this.a = 1
└── a 的 setter (defineReactive 内部)
└── dep.notify() (observer/dep.js)
└── subs[i].update() (observer/watcher.js)
└── queueWatcher (scheduler.js)
└── nextTick (scheduler.js)
└── flushSchedulerQueue (scheduler.js)
└── watcher.run (observer/watcher.js)
└── watcher.get (observer/watcher.js)
└── 组件重新渲染 (render 函数)
关键步骤详解:
-
setter触发:- 检查新值
1是否与旧值不同(newVal !== oldVal)。 - 若不同,调用
dep.notify()通知所有依赖。
- 检查新值
-
dep.notify():- 遍历
dep.subs(存储所有订阅该属性的 Watcher)。 - 调用每个
watcher.update()。
- 遍历
-
watcher.update():- 将 Watcher 加入异步队列(
queueWatcher)。 - 通过
nextTick异步执行更新。
- 将 Watcher 加入异步队列(
-
flushSchedulerQueue:- 遍历队列中的 Watcher,调用
watcher.run()。 watcher.run()→watcher.get()→ 重新执行组件的render函数。
- 遍历队列中的 Watcher,调用
-
重新渲染:
render函数执行时访问a,触发getter重新收集依赖。- 生成新的虚拟 DOM,对比差异后更新真实 DOM。
3. 依赖收集机制
在首次渲染和后续更新时,getter 负责收集依赖:
// getter 调用栈:
组件访问 a (render 函数)
└── a 的 getter (defineReactive 内部)
└── Dep.target (全局唯一 Watcher)
└── dep.depend() (observer/dep.js)
└── 将当前 Watcher 添加到 dep.subs
关键点:
Dep.target:全局唯一变量,指向当前正在执行的 Watcher(如渲染 Watcher)。dep.depend():将当前 Watcher 加入dep.subs,建立属性 → Watcher的依赖关系。
4. 异步更新队列
Vue 使用异步队列合并更新:
// nextTick 流程:
queueWatcher (scheduler.js)
└── nextTick (util/next-tick.js)
└── 异步任务 (Promise/MutationObserver/setTimeout)
└── flushSchedulerQueue (scheduler.js)
优化逻辑:
- 多次修改
a会被合并为一次更新(避免重复渲染)。 - 通过
nextTick确保在 DOM 更新后执行回调。
Vue 3 Proxy 版本的差异
若使用 Vue 3(基于 Proxy):
- 初始化:通过
reactive创建响应式代理。 - 修改值:直接触发
Proxy.set拦截器,后续流程类似(依赖收集、异步更新)。 - 核心差异:
- 无需
Object.defineProperty,支持动态属性。 - 依赖收集通过
Track操作,更新通过Trigger操作。
- 无需
总结
| 阶段 | 核心操作 | 关键函数/类 |
|---|---|---|
| 初始化 | 为 a 创建响应式 getter/setter | defineReactive、Dep |
| 修改值 | 触发 setter → 通知依赖 | dep.notify() |
| 依赖更新 | 异步队列合并更新 | queueWatcher、nextTick |
| 重新渲染 | 执行 render 函数 | Watcher.run() |
整个流程体现了 Vue 响应式系统的核心:依赖收集(getter)和 派发更新(setter),通过 异步队列 优化性能。