- Data通过Observer转换成getter/setter形式追踪数据变化。
- 浏览器从Watcher读取数据,Watcher触发getter,getter将Watcher收集到依赖。注意,这里的Watcher是组件,不是一个具体的DOM节点。
- 数据发生变化,触发setter,通知Dep中的依赖(Watcher)。
- Watcher收到通知,向浏览器发送通知,可能触发视图更新,也可能触发用户的回调。
defineReactive
Object可以通过Object.defineProperty将属性转化成getter/setter 的形式来追踪变化。读取数据时会触发getter,修改数据时会触发setter。
需要在getter中收集有哪些依赖使用了数据,当setter被触发时,去通知getter中收集的依赖数据发生了变化。
function defineReactive(data, key, val) {
let dep = new Dep();
Object.defineProperty(data, key, {
enumerable: true,
configurable: true,
get: function () {
// 收集依赖
dep.depend();
return val;
},
set: function (newVal) {
if (val === newVal) {
return;
}
val = newVal;
// 触发依赖
dep.notify();
},
});
}
Watcher
watcher的原理是先把自己设置到全局唯一的指定位置(例如window.target),然后读取数据。因为读取了数据,所以会触发这个数据的getter。接着,在getter中就会从全局唯一的哪个位置读取当前正在读取数据的watcher,并把这个watcher收集到Dep中去。通过这样的方式watcher可以主动去订阅任意一个数据的变化。
export default class Watcher {
constructor(vm, expOrFn, cb) {
this.vm = vm;
// 执行this.getter(),就可以读取data.a.b.c的内容
this.getter = parsePath(expOrFn);
this.cb = cb;
this.value = this.get();
}
get() {
window.target = this;
let value = this.getter.call(this.vm, this.vm);
window.target = undefined;
return value;
}
update() {
const oldValue = this.value;
this.value = this.get();
this.cb.call(this.vm, this.value, oldValue);
}
}
// Watcher的一个使用方式
vm.$watch("a.b.c", function (newVal, oldVal) {
// 做点什么
});
// 解析路径
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;
};
}
Dep
收集依赖需要为依赖找一个存储依赖的地方,为此我们创建了Dep,它用来收集依赖、删除依赖和向依赖发送消息等
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); // watcher
}
}
notify() {
const subs = this.subs.slice();
for (let i = 0; i < subs.length; i++) {
subs[i].update();
}
}
}
function remove(arr, item) {
if (arr.length) {
const index = arr.indexOf(item);
if (index > -1) {
return arr.splice(index, 1);
}
}
}