Vue2 响应式系统实现
手写 defineReactive、Dep 和 Watcher
问题
从零实现 Vue2 的简易响应式系统,包含 defineReactive、Dep 和 Watcher。
解答
实现 Dep(依赖收集器)
// 当前正在执行的 Watcher
Dep.target = null
class Dep {
constructor() {
// 存储所有订阅者
this.subs = []
}
// 添加订阅者
addSub(watcher) {
this.subs.push(watcher)
}
// 收集依赖
depend() {
if (Dep.target) {
this.addSub(Dep.target)
}
}
// 通知所有订阅者更新
notify() {
this.subs.forEach(watcher => watcher.update())
}
}
实现 defineReactive
function defineReactive(obj, key, val) {
// 每个属性都有自己的 Dep 实例
const dep = new Dep()
// 递归处理嵌套对象
observe(val)
Object.defineProperty(obj, key, {
enumerable: true,
configurable: true,
get() {
// 收集依赖
dep.depend()
return val
},
set(newVal) {
if (newVal === val) return
val = newVal
// 新值可能是对象,需要递归观察
observe(newVal)
// 通知更新
dep.notify()
}
})
}
// 观察对象的所有属性
function observe(obj) {
if (typeof obj !== 'object' || obj === null) return
Object.keys(obj).forEach(key => {
defineReactive(obj, key, obj[key])
})
}
实现 Watcher
class Watcher {
constructor(getter, callback) {
this.getter = getter // 获取数据的函数
this.callback = callback // 数据变化时的回调
this.value = this.get() // 初始化时触发依赖收集
}
get() {
// 设置当前 Watcher
Dep.target = this
// 执行 getter,触发属性的 get,完成依赖收集
const value = this.getter()
// 清空当前 Watcher
Dep.target = null
return value
}
update() {
const oldValue = this.value
this.value = this.get()
this.callback(this.value, oldValue)
}
}
完整示例
// 测试数据
const data = {
name: 'Vue',
info: {
version: 2
}
}
// 使数据响应式
observe(data)
// 创建 Watcher 监听 name
new Watcher(
() => data.name,
(newVal, oldVal) => {
console.log(`name 变化: ${oldVal} -> ${newVal}`)
}
)
// 创建 Watcher 监听 info.version
new Watcher(
() => data.info.version,
(newVal, oldVal) => {
console.log(`version 变化: ${oldVal} -> ${newVal}`)
}
)
// 测试
data.name = 'Vue2' // 输出: name 变化: Vue -> Vue2
data.info.version = 3 // 输出: version 变化: 2 -> 3
关键点
- Dep.target 是全局变量,用于在 getter 中获取当前 Watcher
- 闭包 让每个属性都有独立的 Dep 实例和 val 值
- 依赖收集时机:Watcher 初始化时执行 getter,触发属性的 get
- Object.defineProperty 的局限:无法检测属性的新增和删除,数组索引修改也无法检测
- 递归观察:嵌套对象和新赋值的对象都需要递归处理
目录