vue依赖收集
特点:
- 多对多的关系,每个属性都有一个dep用来收集watcher
- dep可以对应多个watcher
- watcher可以对应多个dep
vue更新策略是以组件为单位的。 当页面初始化时,new Watcher(),当前新增一个渲染watcher,数据变化会更新这个watcher的状态。
渲染watcher是什么? 是vue内部定义的watcher的实例。
watcher分类:
1.内部watcher computed之类
2.在watch属性中用户自己定义的
3.渲染watcher —>render-watcher
依赖收集流程
初始化组件时运行
//init.js
//初始化挂载组件页面
Vue.prototype.$mount = function(el) {
...
// 需要挂载这个组件
mountComponent(vm, el);
}
调用mountComponent,创建watcher
//lifecycle.js
//渲染页面时会创建渲染watcher
import Watcher from 'observe/watcher.js'
export function mountComponent(vm, el) {
...
let updateComponent = ()=>{
vm._update(vm._render()); // 渲染 、 更新
}
// 初始化就会创建watcher
let watcher = new Watcher(vm,updateComponent,()=>{
callHook(vm,'updated')
},true);
}
定义watcher类 new Watcher会调用类中定义的this.get(),通过pushTarget(),将当前的渲染watcher挂载在dep.target属性上( Dep.target 是全局变量,所以同一时间只有一个渲染watcher存在)。 let result = this.getter(); this.getter()渲染当前页面并取值。 取值会走到observer.js中的get函数。
import Dep from "./dep.js";
class Watcher { // vm.$watch
// vm实例
// exprOrFn 是 vm._update(vm._render())
constructor(vm, exprOrFn, cb, options) {
...
if (typeof exprOrFn == 'function') {
this.getter = exprOrFn
}
// 默认会先调用一次get方法 ,进行取值 将结果保留下来
this.value = this.get(); // 默认会调用get方法
}
get() {
// Dep.target = watcher 挂载全局变量
pushTarget(this); // 当前watcher实例
let result = this.getter(); // 调用exprOrFn 渲染页面 取值(执行了get方法)
popTarget(); //渲染完成后 将当前全局watcher删掉了
return result
}
}
pushTarget将当前watcher挂载在dep.target属性上。
//observe/dep.js
//操作dep.target
Dep.target = null; // 静态属性 就一份
export function pushTarget(watcher){
Dep.target = watcher;// 保留watcher
}
取值会走get函数。get中判断当前如果是渲染watcher,就把当前的渲染watcher压入subs中。
if(dep.target){
dep.depend()
}
//
this.subs = [];
this.subs.push(Dep.target)
等该属性发生变化了,在set中调用dep.notify();通知该属性的dep.subs的watcher逐一拿出去执行。
//当data发生改变时,改变视图。
set(newValue){ // 依赖更新
if(newValue === value) return;
observe(newValue); // 如果用户将值改为对象继续监控
value = newValue;
dep.notify(); // 异步更新 防止频繁操作
}
//notify 拿出this.subs数组进行执行。
notify(){
this.subs.forEach(watcher=>watcher.update());
}
当依赖收集成功并且页面渲染完成,将当前的Dep.target = null;这样保证了非渲染watcher取值不会被收集依赖。
export function popTarget(){
Dep.target = null; // 将变量删除掉
}
总结: 在初始化组件时生成renderWatcher。
渲染时需要取值,取值时判断当前如果是renderWatcher,就进行收集,存到dep.subs中。subs中一般会存有多个renderWatcher。
这样就形成了一个data和renderWatcher之间的发布订阅的关系。后面data的改变会直接出发set中的notify(),通知renderWatcher重新渲染页面。