让数据变得可观测

我们都知道 Vue2 中是通过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;
},
});

依赖收集

让 object 数据变的可观测。变的可观测以后,我们就能知道数据什么时候发生了变化,那么当数据发生变化时,我们去通知视图更新就好了。那么问题又来了,视图那么大,我们到底该通知谁去变化?总不能一个数据变化了,把整个视图全部更新一遍吧,这样显然是不合理的。此时,你肯定会想到,视图里谁用到了这个数据就更新谁呗。对!你想的没错,就是这样.

在 Vvue 中是通过实现的一个 Dep 类来实现依赖收集 源码如下

// 源码位置:src/core/observer/dep.js
export default class Dep {
constructor() {
this.subs = [];
}

addSub(sub) {
this.subs.push(sub);
}
// 删除一个依赖
removeSub(sub) {
remove(this.subs, sub);
}
// 添加一个依赖
depend() {
if (window.target) {
this.addSub(window.target);
}
}
// 通知所有依赖更新
notify() {
const subs = this.subs.slice();
for (let i = 0, l = subs.length; i < l; i++) {
subs[i].update();
}
}
}

/**
* Remove an item from an array
*/
export function remove(arr, item) {
if (arr.length) {
const index = arr.indexOf(item);
if (index > -1) {
return arr.splice(index, 1);
}
}
}

在上面的依赖管理器 Dep 类中,我们先初始化了一个 subs 数组,用来存放依赖,并且定义了几个实例方法用来对依赖进行添加,删除,通知等操作。

有了依赖管理器后,我们就可以在 getter 中收集依赖,在 setter 中通知依赖更新了,代码如下:

function defineReactive(obj, key, val) {
if (arguments.length === 2) {
val = obj[key];
}
if (typeof val === "object") {
new Observer(val);
}
const dep = new Dep(); //实例化一个依赖管理器,生成一个依赖管理数组dep
Object.defineProperty(obj, key, {
enumerable: true,
configurable: true,
get() {
dep.depend(); // 在getter中收集依赖
return val;
},
set(newVal) {
if (val === newVal) {
return;
}
val = newVal;
dep.notify(); // 在setter中通知依赖更新
},
});
}

在上述代码中,我们在 getter 中调用了 dep.depend()方法收集依赖,在 setter 中调用 dep.notify()方法通知所有依赖更新

Watcher

谁用到了数据,谁就是依赖,我们就为谁创建一个Watcher实例。在之后数据变化时,我们不直接去通知依赖更新,而是通知依赖对应的 Watch 实例,由Watcher实例去通知真正的视图

export default class Watcher {
constructor(vm, expOrFn, cb) {
this.vm = vm;
this.cb = cb;
this.getter = parsePath(expOrFn);
this.value = this.get();
}
get() {
window.target = this;
const vm = this.vm;
let value = this.getter.call(vm, vm);
window.target = undefined;
return value;
}
update() {
const oldValue = this.value;
this.value = this.get();
this.cb.call(this.vm, this.value, oldValue);
}
}

/**
* Parse simple path.
* 把一个形如'data.a.b.c'的字符串路径所表示的值,从真实的data对象中取出来
* 例如:
* data = {a:{b:{c:2}}}
* parsePath('a.b.c')(data) // 2
*/
const bailRE = /[^\w.$]/;
export function parsePath(path) {
if (bailRE.test(path)) {
return;
}
const segments = path.split(".");
return function (obj) {
for (let i = 0; i < segments.length; i++) {
if (!obj) return;
obj = obj[segments[i]];
}
return obj;
};
}

Watcher先把自己设置到全局唯一的指定位置(window.target),然后读取数据。因为读取了数据,所以会触发这个数据的 getter。接着,在 getter 中就会从全局唯一的那个位置读取当前正在读取数据的 Watcher,并把这个 watcher 收集到 Dep 中去。收集好之后,当数据发生变化时,会向 Dep 中的每个 Watcher 发送通知。通过这样的方式,Watcher 可以主动去订阅任意一个数据的变化

以上,就彻底完成了对 Object 数据的侦测,依赖收集,依赖的更新等所有操作