VUE响应式原理:
先准备一个用来发布订阅事件的类 Dep
,该类中定义一个数组 subs
用来存放订阅信息,然后在 Dep
上添加一个全局标识 target
用来配置 Watcher
。
vue
在实例化对象的时候会先遍历递归 data
配置项中的属性,用 Object.defineProperty
给每一个属性加上 getter
和 setter
方法。
在 getter
方法中,调用 Dep
的添加方法将 Dep.target
push
到 subs
中(实现订阅),在 setter
方法中遍历 subs
数组(实现发布)。
vue
在进行模板解析时,遇到双大括号语法,会调用 Watcher
类并把括号内的属性和更新操作传过去,在 Watcher
类中将 this
指向 Dep.target
,然后添加参数中的属性和方法,然后触发属性的 getter
添加监听,最后将 Dep.target
置空。
代码:
// 通过 Dep 解耦
class Dep {
constructor() {
this.subs = []
}
addSub(sub) {
// sub 是 Watcher 实例
this.subs.push(sub)
}
notify() {
this.subs.forEach(sub => {
sub.update()
})
}
}
// 全局属性,通过该属性配置 Watcher
Dep.target = null
function update(value) {
document.querySelector('div').innerText = value
}
class Watcher {
constructor(obj, key, cb) {
// 将 Dep.target 指向自己
// 然后触发属性的 getter 添加监听
// 最后将 Dep.target 置空
Dep.target = this
this.cb = cb
this.obj = obj
this.key = key
this.value = obj[key]
Dep.target = null
}
update() {
// 获得新值
this.value = this.obj[this.key]
// 调用 update 方法更新 Dom
this.cb(this.value)
}
}
var data = { name: 'yck' }
observe(data)
// 模拟解析到 `{{name}}` 触发的操作
new Watcher(data, 'name', update)
// update Dom innerText
data.name = 'yyy'
function defineReactive(obj, key, val) {
// 递归子属性
observe(val)
let dp = new Dep()
Object.defineProperty(obj, key, {
enumerable: true,
configurable: true,
get: function reactiveGetter() {
console.log('get value')
// 将 Watcher 添加到订阅
if (Dep.target) {
dp.addSub(Dep.target)
}
return val
},
set: function reactiveSetter(newVal) {
console.log('change value')
val = newVal
// 执行 watcher 的 update 方法
dp.notify()
}
})
}