vue源码之依赖收集

431 阅读2分钟

vue依赖收集

特点:

  1. 多对多的关系,每个属性都有一个dep用来收集watcher
  2. dep可以对应多个watcher
  3. 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重新渲染页面。