本文简述Vue.js中watch的实现,代码均为为叙述而简化的代码。
下面是常见的Vue代码,本文以此为例:
const vm = new Vue({
data: {
a: 1
},
watch: {
a: function (val, oldVal) {
console.log('a你变了')
}
}
})
setTimeout( () => {
vm.a = 2
}, 2000)
因Vue实例中watch了a,所以两秒后a的值更改时,控制台将打印:'a你变了'。
Vue是如何做到这件事的呢?
当对Vue实例添加watch时,Vue会new一个Watcher类的对象即watcher,以下列举watcher对象的几个关键属性:
watcher: {
// API中watch传入的回调函数,在Watcher.prototype.update方法中调用
cb: function() {...},
// expOrFn可理解为对象的key(例中{a: 1}的a)
expOrFn: 'a',
// 调用Watcher.prototype.get方法,用以记录旧值
value: this.get()
}
上面this.get()调用的Watcher.prototype.get方法:
Watcher.prototype.get = function () {
// 在下文添加订阅者的关键代码。Dep类稍后解释
Dep.target = this
// 获取数据对象的当前值(例中{a: 1}初始化调用时为1)
const value = this.vm._data[this.expOrFn]
// after get value, set null
Dep.target = null
return value
}
Vue在初始化时,先对data属性中的键值创建了观察者Observer对象。 在Watcher.prototype.get方法中,因为this.vm._data[this.expOrFn]代码对对象值进行了获取,所以调用了Observer中为data定义的getter,getter中添加了订阅者。
以下是Observe为对象添加getter,setter的代码:
const dep = new Dep()
Object.defineProperty({a: 1}, a, {
enumerable: true,
configurable: true,
get: () => {
// 在之前Watcher.prototype.get中设置了Dep.target
if (Dep.target) {
// 添加订阅者
dep.depend()
}
return val
},
set: (newVal) => {
// 如果新值和旧值一样则不作操作
if (val === newVal) {
return
}
val = newVal
// 发送通知
dep.notify()
}
})
如上文所见,Dep类是维护watcher的一个类,它挂载了一个叫subs的数组,当调用
dep.depend()时,就往dep.subs中push了watcher。当调用dep.notify()通知更新时,Dep会循环dep.subs中的watcher并调用watcher.update方法。
因此,当我们调用vm.a = 2时,Observe的setter会执行并发送通知,watcher.update方法会被触发。update方法就显而易见了:
Watcher.prototype.update = function (dep) {
// 获取当前值即更新后的值,此时自然也添加了新的watcher
const value = this.get()
// 此watcher中存储的旧值
const oldValue = this.value
// 更新值
this.value = value
if (value !== this.value || value !== null) {
// 传入的回调函数被调用,此时打印 'a你变了'
this.cb.call(this.vm, value, oldValue)
// 从dep.subs中删除此watcher
dep.subs.shift()
}
}
以上。