与依赖收集相关的模块是: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的数据绑定原理已经分析完了,再来看一眼这张图片,是不是已经有所感悟了呢