Vue2 响应式原理
理解 Object.defineProperty、依赖收集和 Watcher 的工作机制
问题
Vue2 是如何实现数据响应式的?请解释 Object.defineProperty、依赖收集和 Watcher 的作用。
解答
整体流程
- Observer:遍历对象,用
Object.defineProperty劫持属性的 getter/setter - Dep:每个属性对应一个 Dep,用于收集依赖(Watcher)
- Watcher:订阅者,数据变化时执行回调
简化实现
// 依赖收集器
class Dep {
constructor() {
this.subs = [] // 存储 watcher
}
addSub(watcher) {
this.subs.push(watcher)
}
// 通知所有 watcher 更新
notify() {
this.subs.forEach(watcher => watcher.update())
}
}
// 当前正在计算的 watcher
Dep.target = null
// 观察者
class Watcher {
constructor(vm, key, callback) {
this.vm = vm
this.key = key
this.callback = callback
// 触发 getter,收集依赖
Dep.target = this
this.value = vm[key]
Dep.target = null
}
update() {
const newValue = this.vm[this.key]
if (newValue !== this.value) {
this.value = newValue
this.callback(newValue)
}
}
}
// 将对象变成响应式
function observe(obj) {
if (typeof obj !== 'object' || obj === null) return
Object.keys(obj).forEach(key => {
defineReactive(obj, key, obj[key])
})
}
// 定义响应式属性
function defineReactive(obj, key, value) {
const dep = new Dep()
// 递归处理嵌套对象
observe(value)
Object.defineProperty(obj, key, {
enumerable: true,
configurable: true,
get() {
// 收集依赖
if (Dep.target) {
dep.addSub(Dep.target)
}
return value
},
set(newValue) {
if (newValue === value) return
value = newValue
// 新值也要变成响应式
observe(newValue)
// 通知更新
dep.notify()
}
})
}
使用示例
const data = { name: 'Vue', count: 0 }
// 让 data 变成响应式
observe(data)
// 创建 watcher,监听 count 变化
new Watcher(data, 'count', (newValue) => {
console.log('count 变化了:', newValue)
})
data.count = 1 // 输出: count 变化了: 1
data.count = 2 // 输出: count 变化了: 2
依赖收集过程
1. new Watcher() 时,设置 Dep.target = 当前 watcher
2. 读取 vm[key],触发 getter
3. getter 中检测到 Dep.target,将 watcher 加入 dep.subs
4. 清空 Dep.target
数据变化时:
1. 触发 setter
2. 调用 dep.notify()
3. 遍历 subs,执行每个 watcher.update()
关键点
- Object.defineProperty 只能劫持已存在的属性,无法检测属性的新增和删除
- Dep 是发布者,每个响应式属性都有一个 Dep 实例
- Watcher 是订阅者,在初始化时触发 getter 完成依赖收集
- Dep.target 是全局变量,用于在 getter 中识别当前 watcher
- 数组需要特殊处理,Vue2 通过重写数组方法(push、pop 等)实现响应式
目录