与依赖收集相关的模块是:Dep实例负责维护属性的依赖列表,Watcher就是Dep实例维护的观察者队列中的观察者
那么什么是依赖呢?用到数据的地方就是依赖。实际上这个依赖指的是watcher,收集依赖就是将watcher添加到dep的过程,依赖更新就是触发watcher的update
当watcher触发getter时,就把这个watcher收集到依赖中,数据发生变化时,就会通知这些watcher去更新
那么就先来看一下Watcher吧
export default class Watcher {
constructor(target, expression, callback) {
this.target = target;
this.getter = parsePath(expression);
this.callback = callback;
this.value = this.get();
}
update() {
this.run();
}
get() {
//进入依赖收集阶段.让全局的Dep.target设置为Watcher本身,那么就是进入依赖收集阶段
Dep.target = this;
const obj = this.target;
var value;
//只要能找,就一直找
try {
value = this.getter(obj);
} finally {
Dep.target = null;
}
return value;
}
run() {
this.getAndInvoke(this.callback)
}
getAndInvoke(cb) {
const value = this.get();
if (value !== this.value || typeof value == 'object') {
const oldValue = this.value;
cb.call(this.target, value, oldValue)
}
}
}
// 作用是将调用链解析出来,例如'a.b.c.d',就会从a:{b: {c: {d: value}}}中拿到value
function parsePath(str) {
var segments = str.split('.');
return (obj) => {
for (let i = 0; i < segments.length; i++) {
if (!obj) return;
obj = obj[segments[i]]
}
return obj;
}
}
这里的Watcher也就是vue中的$watcher那个API,然后就是Dep
export default class Dep{
constructor(){
//用数组存储自己的订阅者。subs是subscribes订阅者的意思
//这是数组里面放的是Watcher的实例
this.subs = [];
}
//添加订阅
addSub(sub){
this.subs.push(sub)
}
//添加依赖
depend(){
//Dep.target我们自己指定的全局的位置,只要是全局唯一没有歧义就行
if(Dep.target){
this.addSub(Dep.target)
}
}
//通知更新
notify(){
//浅克隆一份
const subs = this.subs.slice();
//遍历
for(let i = 0,l = subs.length;i<l;i++){
subs[i].update();
}
}
}
注意Dep.target这个东西,这个东西非常巧妙,当watcher在实例化时会将Dep.target指向当前watcher,此时如果触发属性getter就会将当前watcher添加到dep中,利用闭包的原理,不同的对象属性维护着自己的dep实例
你现在应该还记得开始的defineReactive,我们在属性描述的getter中收集依赖(当试图读取属性时就会被记录),在setter中通知依赖更新
这时候defineReactive又要做出改变了
export default function defineReactive(data,key,val){
const dep = new Dep();
if(arguments.length == 2){
val = data[key];
}
//子元素要进行observe,形成递归,多个函数循环调用
let childOb = observe(val);
Object.defineProperty(data,key,{
enumerable:true,
//可以被配置,比如可以被delete
configurable:true,
//getter
get() {
console.log(`打开${key}属性`)
//如果现在处于依赖收集阶段
if(Dep.target){
dep.depend();
if(childOb){
childOb.dep.depend()
};
}
return val;
},
//setter
set(newValue) {
if(val === newValue){
return;
}
val = newValue;
//当设置新值,这个新值也要被observe
childOb = observe(newValue)
//发布订阅模式,通知dep
dep.notify();
}
});
}
到现在为止,Vue的数据绑定原理已经分析完了,再来看一眼这张图片,是不是已经有所感悟了呢
本文正在参与「掘金小册免费学啦!」活动, 点击查看活动详情