依赖收集
在 getter 中收集依赖,在 setter 中触发依赖
fucntion defineReactive(data, key, val) {
const dep = [] //新增
Object.defineProperty(data, key {
enumerable: true,
configurable: true,
get: fucntion() {
dep.push(window.target) //新增
return val;
},
set: function(newVal) {
if(val === newVal) {
return ;
}
for(let i = 0;i < dep.length; i++) {
dep[i](newVal, val);
}
val = newVal;
}
})
}
这里我们新增了数组dep,用来存储被收集的依赖。
然后在set被触发时,循环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);
}
}
notify () {
const subs = this.subs.slice();
for(let i = 0, len = subs.length;i<len;i++) {
subs[i].update();
}
}
}
function remove (arr, item) {
if(arr.length) {
const index = arr.indexOf(item);
if(index > -1) {
return arr.splice(index, 1);
}
}
}
function defineReactive (data, key, val) {
const dep = new Dep();
Object.defineProperty(data, key, {
enumerable: true,
configurable: true,
get: function () {
dep.depend();
return val;
},
set: function () {
if(val === newVal) {
return;
}
val = newVal;
dep.notify(); //新增
}
})
}
依赖收集到哪了?收集到Dep中了。
Watcher
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);
}
}
先在window.target赋一个this, 也就是当前watcher实例,然后再读一下 data.a.b.c的值,这肯定会触发getter。
触发了getter, 就会触发依赖收集逻辑,按上文介绍的,会从window.target中读取一个依赖并添加到Dep中。
这就导致,只要先在window.target中赋一个this,然后再读一下值,去触发getter,就可以把this主动加到keypath的Dep中。
parsePath 实现也很机智
/**
* 解析简单路径
**/
const bailRE = /[^\w.$]/;
export function parsePath (path) {
if(bailRE.test(psth)) {
return ;
}
const segments = path.split('.');
return function (obj) {
for(let i = 0,len = segments.length;i<len;i++) {
if(!obj) return ;
obj = obj[segments[i]];
}
return obj;
}
}