Vue的双向绑定是通过数据劫持和发布-订阅模式实现的
在vue中每个响应式属性都会有一个dep,存放订阅它的watcher实例,Watcher实例会监听数据的变化。当数据发生变化时,Watcher实例会通知对应的Dep实例,Dep实例会通知所有订阅它的Watcher实例进行更新
在Vue中,数据劫持是通过Object.defineProperty()方法来实现的。Vue会遍历组件实例中的每个数据属性,并将其转化为getter和setter方法。当组件实例中的数据属性被读取时,就会触发getter方法;当数据属性被修改时,就会触发setter方法。在setter方法中,Vue会通知对应的Watcher实例进行更新。
// 数据劫持
function defineReactive(obj, key, val) {
const dep = new Dep();
Object.defineProperty(obj, key, {
enumerable: true,
configurable: true,
get: function reactiveGetter() {
dep.depend();
return val;
},
set: function reactiveSetter(newVal) {
if (val === newVal) {
return;
}
val = newVal;
dep.notify();
}
});
}
// 发布-订阅模式
class Dep {
constructor() {
this.subs = [];
}
addSub(sub) {
this.subs.push(sub);
}
depend() {
if (Dep.target) {
Dep.target.addDep(this);
}
}
notify() {
const subs = this.subs.slice();
for (let i = 0, l = subs.length; i < l; i++) {
subs[i].update();
}
}
}
// Watcher实例
class Watcher {
constructor(vm, expOrFn, cb) {
this.vm = vm;
this.cb = cb;
this.getter = parsePath(expOrFn);
this.value = this.get();
}
get() {
Dep.target = this;
const value = this.getter.call(this.vm, this.vm);
Dep.target = null;
return value;
}
update() {
const oldValue = this.value;
this.value = this.get();
this.cb.call(this.vm, this.value, oldValue);
}
addDep(dep) {
dep.addSub(this);
}
}
在上述代码中
Dep.target是一个全局变量,它的作用是用来存储当前正在处理的Watcher实例。当组件实例中的数据属性被读取时,会触发getter方法,并且Dep.target会被设置为当前正在处理的Watcher实例,这样就可以将该Watcher实例添加到对应的Dep实例中进行订阅----进行一个收集依赖
当数据属性被修改时,会触发setter方法,并且Dep实例会通知所有订阅它的Watcher实例进行更新-----进行触发依赖