1、数据变化其实是借助了Object.defineProperty方法,先来一个栗子:
let car = {}
let val = 3000
Object.defineProperty(car, 'price', {
enumerable: true,
configurable: true,
get(){
console.log('price属性被读取了')
return val
},
set(newVal){
console.log('price属性被修改了')
val = newVal
}
})
car.price; // 控制台打印:price属性被读取了
car.price = val; // 控制台会打印:price属性被修改了
数据读取时就调用get方法,数据被修改时调用set方法。
那接下来我们主要在get和set里面操作即可。
2、Vue中把Object.defineProperty依赖于Observer 类中。然后判断数据类型,Observer中通过walk调用defineReactive,再调用Object.defineProperty实现观测。也就是说,只要我们将一个object传到observer中,那么这个object就会变成可观测的、响应式的object。
栗子:
let car = new Observer({
'brand':'BYD',
'price':6000
})
源码:
var Observer = function Observer (value) {
this.value = value;
this.dep = new Dep();
this.vmCount = 0;
def(value, '__ob__', this);
if (Array.isArray(value)) {
if (hasProto) {
protoAugment(value, arrayMethods);
} else {
copyAugment(value, arrayMethods, arrayKeys);
}
this.observeArray(value);
} else {
this.walk(value);
}
};
Observer.prototype.walk = function walk (obj) {
var keys = Object.keys(obj);
for (var i = 0; i < keys.length; i++) {
defineReactive$$1(obj, keys[i]);
}
};
function defineReactive$$1 (
...详情可以查看源码
Object.defineProperty(obj, key, {
enumerable: true,
configurable: true,
get: function reactiveGetter () {
...
}
return value
},
set: function reactiveSetter (newVal) {
...
}
});
}
3、问题来了,数据目前是可以观测的,但是我们目的是更新到视图中,然而更新那个视图呢?这就需要我们去收集依赖的元素。那么当这个数据发生变化的时候,我们就去它对应的依赖数组中,把每个依赖都通知一遍,告诉他们:“你们依赖的数据变啦,你们该更新啦!”。
var Dep = function Dep () {
this.id = uid++;
this.subs = [];
};
Dep.prototype.addSub = function addSub (sub) {
this.subs.push(sub);
};
// 删除一个依赖
Dep.prototype.removeSub = function removeSub (sub) {
remove(this.subs, sub);
};
// 添加一个依赖
Dep.prototype.depend = function depend () {
if (Dep.target) {
Dep.target.addDep(this);
}
};
// 通知所有依赖更新
Dep.prototype.notify = function notify () {
// stabilize the subscriber list first
var subs = this.subs.slice();
if (!config.async) {
subs.sort(function (a, b) { return a.id - b.id; });
}
for (var i = 0, l = subs.length; i < l; i++) {
subs[i].update();
}
};
然后在defineReactive中的defineProperty方法里面的实现:在getter中收集依赖,在setter中通知依赖更新。
Object.defineProperty(obj, key, {
enumerable: true,
configurable: true,
get: function reactiveGetter () {
...
if (Dep.target) {
//添加一个依赖
dep.depend();
...
}
return value
},
set: function reactiveSetter (newVal) {
...
// 通知所有依赖更新
dep.notify();
}
});
4、虽然我们一直在说”谁用到了这个数据谁就是依赖“,但是这仅仅是在口语层面上,那么反应在代码上该如何来描述这个”谁“呢?所以要安装一双眼睛来监测,也就是Watcher类。
换句话说就是:谁用到了数据,谁就是依赖,我们就为谁创建一个Watcher实例。在之后数据变化时,我们不直接去通知依赖更新,而是通知依赖对应的Watch实例,由Watcher实例去通知真正的视图。
Watcher.prototype.get = function get () {
pushTarget(this);
var value;
var vm = this.vm;
try {
value = this.getter.call(vm, vm);
} catch (e) {
if (this.user) {
handleError(e, vm, ("getter for watcher \"" + (this.expression) + "\""));
} else {
throw e
}
} finally {
// "touch" every property so they are all tracked as
// dependencies for deep watching
if (this.deep) {
traverse(value);
}
popTarget();
this.cleanupDeps();
}
return value
};
简单总结一下就是:Watcher先把自己设置到全局唯一的指定位置(window.target),然后读取数据。因为读取了数据,所以会触发这个数据的getter。接着,在getter中就会从全局唯一的那个位置读取当前正在读取数据的Watcher,并把这个watcher收集到Dep中去。收集好之后,当数据发生变化时,会向Dep中的每个Watcher发送通知。通过这样的方式,Watcher可以主动去订阅任意一个数据的变化。