Vue2 响应式原理解析
本文旨在通过源码视角,清晰地阐述 Vue 2.x 响应式原理的核心机制。Vue 的响应式本质上是一个自动化的观察者模式实现,它在数据变动与视图更新之间建立了一座无形的桥梁。
1. 核心模型:观察者模式
Vue 的响应式系统由三个核心角色协同工作:
| 角色 | 源码类名 | 形象比喻 | 核心职责 |
|---|---|---|---|
| 劫持者 | Observer | 变压器 | 递归遍历数据,利用 Object.defineProperty 将属性转化为 Getter/Setter。 |
| 发布者 | Dep | 通信兵 | 每一个响应式属性唯一的依赖管理器。负责收集当前正在读取该属性的 Watcher,并在属性变化时发送通知。 |
| 订阅者 | Watcher | 执行者 | 代表一个具体的任务(如组件渲染、计算属性求值、watch 回调)。当依赖的数据变化时,被动触发更新逻辑。 |
2. 响应式系统生命周期
阶段一:初始化数据劫持 (Initialization)
在 Vue 实例初始化时,Observer 会处理 data 中的每一个属性。
// src/core/observer/index.ts
export class Observer {
constructor(public value: any) {
this.dep = new Dep() // 为对象/数组本身也创建一个 Dep (用于 Vue.set)
def(value, '__ob__', this) // 标记已观察
if (Array.isArray(value)) {
// 数组特殊处理:拦截变异方法
augment(value, arrayMethods)
this.observeArray(value)
} else {
// 对象处理:深度遍历属性
this.walk(value)
}
}
walk(obj: Object) {
const keys = Object.keys(obj)
for (let i = 0; i < keys.length; i++) {
defineReactive(obj, keys[i])
}
}
}
阶段二:依赖收集 (Dependency Collection)
这是最巧妙的部分。当一个 Watcher(如渲染 Watcher)开始执行任务时,它会触发数据的 Getter。
原理图:依赖收集流程
flowchart TD
subgraph "订阅阶段 (Getter)"
A["Watcher 执行任务 (如渲染)"] --> B["Push 到全局 Dep.target"]
B --> C["访问数据 (obj.key)"]
C --> D["触发 Getter"]
D --> E["调用 dep.depend()"]
E --> F["Watcher 与 Dep 互换信息"]
F --> G["Dep 将 Watcher 加入 subs 列表"]
G --> H["Pop 出 Dep.target"]
end
style B fill:#f9f,stroke:#333
style E fill:#bbf,stroke:#333
style G fill:#dfd,stroke:#333
核心代码实现:
// src/core/observer/index.ts -> defineReactive
Object.defineProperty(obj, key, {
get: function reactiveGetter() {
// 关键点:Dep.target 存放着当前正在运行的 Watcher
if (Dep.target) {
dep.depend() // 收集依赖
if (childOb) {
childOb.dep.depend() // 处理嵌套对象
if (Array.isArray(value)) dependArray(value) // 处理数组
}
}
return value
}
})
阶段三:派发更新 (Dispatch Update)
当数据被修改时,Setter 被触发,通知所有订阅了该属性的 Watcher。
原理图:派发更新流程
flowchart TD
subgraph "通知阶段 (Setter)"
A["修改数据 (obj.key = newVal)"] --> B["触发 Setter"]
B --> C{"数据是否真的变化?"}
C -- "否" --> D["退出"]
C -- "是" --> E["更新闭包内的 val"]
E --> F["对新值进行 observe(newVal)"]
F --> G["调用 dep.notify()"]
G --> H["遍历所有 subs (Watcher)"]
H --> I["调用 watcher.update()"]
I --> J["进入异步更新队列 (Scheduler)"]
end
style G fill:#f96,stroke:#333
style I fill:#bbf,stroke:#333
核心代码实现:
// src/core/observer/index.ts -> defineReactive
set: function reactiveSetter(newVal) {
if (newVal === value) return
value = newVal // 更新值
childOb = observe(newVal) // 新值也需要变成响应式
dep.notify() // 核心:通知订阅者
}
// src/core/observer/dep.ts
notify() {
const subs = this.subs.slice()
for (let i = 0, l = subs.length; i < l; i++) {
subs[i].update() // 触发每一个订阅者的 update 方法
}
}
3. 数组的特殊拦截
由于 Object.defineProperty 无法监听数组索引的变化,Vue 通过原型链覆盖的方式拦截了 7 个变异方法。
流程说明:
- 创建一个以
Array.prototype为原型的拦截对象。 - 重写
push,pop,shift,unshift,splice,sort,reverse。 - 在这些方法执行后,通过获取数组身上的
__ob__(即Observer实例),手动调用ob.dep.notify()。
// src/core/observer/array.ts 核心逻辑
methodsToPatch.forEach(function (method) {
const original = arrayProto[method]
def(arrayMethods, method, function mutator(...args) {
const result = original.apply(this, args) // 执行原方法
const ob = this.__ob__
let inserted
// 针对新增元素,也需要进行响应式观察
if (method === 'push' || method === 'unshift') inserted = args
else if (method === 'splice') inserted = args.slice(2)
if (inserted) ob.observeArray(inserted)
ob.dep.notify() // 手动通知更新
return result
})
})
4. 总结:响应式系统的闭环
- 自动化:通过 Getter/Setter 实现了无需显式调用
setState的数据绑定。 - 粒度精准:每个属性拥有自己的
Dep,只有依赖该属性的Watcher才会更新,避免了不必要的渲染。 - 性能优化:
Watcher的更新并非同步执行,而是通过queueWatcher推入微任务队列,在同一事件循环中多次修改数据只会触发一次视图更新。