Vue依赖收集原理

82 阅读4分钟

关于Dep 和 Watcher的 关系

先捋一下,Vue源码的执行逻辑

一、关于 computed 和 watch 中的 Watcher

1. 首先开始初始化

一开始初始化Data(initData函数)的时候, 执行逻辑为

observe -> new Observer(data) -> defineReactive -> get() ->

defineReactive为 如下


function defineReactive(data,key,value){
    let dep = new Dep(); // 这个dep 是为了给对象使用的
    // 这里这个value可能是数组 也可能是对象 ,返回的结果是observer的实例,当前这个value对应的observer
    let childOb = observe(value); // 数组的observer实例
    Object.defineProperty(data,key,{
        configurable:true,
        enumerable:true,
        get(){ //  获取值的时候做一些操作 
           // 每个属性都对应着自己的watcher
            if(Dep.target) { // 如果当前有watcher 
                dep.depend(); // 意味着我要将watcher存起来
                if(childOb){ // *******数组的依赖收集*****
                    childOb.dep.depend(); // 收集了数组的相关依赖

                    // 如果数组中还有数组
                    if(Array.isArray(value)){
                        dependArray(value);
                    }

                }
            }
            return value;
        },
        set(newValue){ // 也可以做一些操作
            // console.log('更新数据')
            if(newValue === value) return;
            observe(newValue); // 继续劫持用户设置的值,因为有可能用户设置的值是一个对象
            value = newValue;
            dep.notify(); // 通知依赖的watcher来进行一个更新操作
        }
    });
}

由于一开始没有Watcher实例,所以函数里面的里面实际上 if(Dep.target) 判断这个的时候 啥也没有. 也就还没依赖收集.

里面的dep.depend();就不需要收集

2. Watcher

Watcher类: src/observer/watcher.js

import {
    pushTarget,
    popTarget
} from './dep.js';

import { queueWatcher } from './schedular'
let id = 0;
class Watcher {
    constructor(vm, exprOrFn, callback, options) {
        this.vm = vm;
        this.callback = callback;
        this.options = options;
        this.sync = options.sync;
        this.user = options.user; // 用来标识watcher的状态
        this.id = id++;
        this.lazy = options.lazy;
        this.dirty = this.lazy;

        if (typeof exprOrFn === 'function') {
            this.getter = exprOrFn; // 将内部传过来的回调函数 放到getter属性上
        } else {
            // 将getter方法 封装成功了取值函数
            this.getter = function() { // 'a.b.c.d'
                let path = exprOrFn.split('.');
                let val = vm;
                for (let i = 0; i < path.length; i++) {
                    val = val[path[i]];
                }
                return val;
            }
        }

        this.depsId = new Set(); // es6中的集合 (不能放重复项)
        this.deps = [];

        // ✅ this.get()核心
        this.value = this.lazy ? undefined : this.get(); // 调用get方法 会让渲染watcher执行
    }

    addDep(dep) { // watcher 里不能放重复的dep  dep里不能放重复的watcher
        let id = dep.id;
        if (!this.depsId.has(id)) {

            this.depsId.add(id);

            this.deps.push(dep);

            dep.addSub(this); // addSub是Dep类里面的方法,这里是将Watcher实例this 收集到 Dep的实例dep中
        }
    }
    get() {
        pushTarget(this); // 把watcher存起来  Dep.target = this
        let value = this.getter.call(this.vm); // 渲染watcher的执行  this.vm = vm 
        popTarget(); // 移除watcher
        return value
    }

    update() {
        if (this.sync) {
            this.run();
        }else if(this.lazy){ // 计算属性依赖的值发生了变化
            this.dirty = true;
        } else {
            queueWatcher(this);
            // console.log(this.id)
            // 等待着 一起来更新 因为每次调用update的时候 都放入了watcher
            // this.get();
        }
    }
    evaluate(){
        this.value = this.get();
        this.dirty = false;
    }
    run() {
        let oldValue = this.value; // 第一次渲染的值
        let newValue = this.get();
        this.value = newValue;
        if (this.user) { // 如果当前是用户watcher 就执行用户定义的callback
            this.callback.call(this.vm, newValue, oldValue);
        }
    }
    depend(){
        let i = this.deps.length;
        while(i--){
            this.deps[i].depend();
        }
    }
}
// 下次课 会带大家看一次vue的源代码

// 在模板中取值时 会进行依赖收集 在更改数据是会进行对应的watcher 调用更新操作
// dep 和 watcher 是一个多对多的关系  dep里存放着相关的watcher 是一个观察者模式
export default Watcher

第一种

当mountComponent函数执行时, 进行组件渲染, 就会执行 new Watcher

执行渲染函数render

内部执行vm_render函数,会去vm取 在template模版中 使用的值, 然后就到了 vm的响应式部分 执行get方法... 以及watcher里面的其他逻辑(同下)

当render渲染 a组件, 将a组件的watcher实例放到Dep.target(全局唯一)上,render渲染的时候 template模版上的 name 就可以取到 name 变量,然后执行 获取name变量 响应式的 get 方法,并且在Dep和Watcher类中, 进行Dep实例收集Watcher,Watcher收集Dep

渲染逻辑render封装在Watcher中,先有watcher渲染的变量、才能找到响应式中的get

由于在外面vm.name取值的时候(非template模版中取值)。也会执行响应式里面的get方法,但是这个时候。不是渲染过程,所以。Dep.Target的值为空,就不会去收集依赖,只有在模板render里面取值,也就是渲染函数render中才会收集依赖

第二种

当初始化 computed的时候, 里面执行new Watcher的时候,

function initComputed(vm, computed) {
    // _computedWatchers 存放着所有的计算属性对应的watcher
    const watchers = vm._computedWatchers = {};

    for (let key in computed) {
        const userDef = computed[key]; // 获取用户的定义函数
        const getter = typeof userDef === 'function' ? userDef : userDef.get;

        // 获得getter函数  lazy:true 表示的就是computed 计算属性

        // 内部会根据lazy属性,不去调用getter
        watchers[key] = new Watcher(vm, getter, ()=>{} , {lazy:true})
        // 计算属性可以直接通过vm来进行取值,所以将属性定义到实例上
        defineComputed(vm,key,userDef); 
    }
}

接上

Watcher内部, 构造函数执行get函数,get函数 会执行 pushTarget 函数,给Dep.target=this赋值,this也就是(watcher实例),

get() {
        pushTarget(this); // 把watcher存起来  Dep.target = this
        let value = this.getter.call(this.vm); // 渲染watcher的执行  this.vm = vm 
        popTarget(); // 移除watcher
        return value
    }

然后在 get函数中 执行了一下 computed 计算属性 用户给的具体的options(也就是initComputed函数里面的getter变量),

所以就会触发options里面的数据的 get 方法(obverse响应式),

相同

然后执行第一部分《首先开始初始化》里面的逻辑

Observer类

get(){ //  获取值的时候做一些操作 
    // 每个属性都对应着自己的watcher
    if(Dep.target) { // 如果当前有watcher 
        dep.depend(); // 意味着我要将watcher存起来
        if(childOb){ // *******数组的依赖收集*****
            childOb.dep.depend(); // 收集了数组的相关依赖

            // 如果数组中还有数组
            if(Array.isArray(value)){
                dependArray(value);
            }

        }
    }
    return value;
},

这里Dep.target有指,接下来执行dep.depend();

Dep类里面的方法


    addSub(watcher){
        this.subs.push(watcher); // 观察者模式
    }

    depend(){
        // 让这个watcher(Dep.target) 记住我当前的dep,如果watcher没存过dep ,dep肯定不能存过watcher
        Dep.target.addDep(this);  // 调用, 这里的this为Dep实例,将Dep实例传到Watcher的addDep方法中
    }

Watcher类里面的方法

 addDep(dep) { // watcher 里不能放重复的dep  dep里不能放重复的watcher
    let id = dep.id;
    if (!this.depsId.has(id)) {

        this.depsId.add(id);

        this.deps.push(dep);

        dep.addSub(this); // addSub是Dep类里面的方法,这里是将Watcher实例this 收集到 Dep的实例dep中
    }
}

让watcher实例addDep收集Dep,

最后在Watcher类, 执行 dep.addSub(this), 让dep收集watcher实例,等于将Watcher实例收集到Dep的subs数组中。

至此完成双向收集。

比喻

将Watcher当成一个侦探,首先应该知道监听谁,也就是addDep,监听那个数据,然后收集一下数据对应的Dep,最后Dep也收集一些监听的类Watcher实例