Vue.js 内部运行机制 (二) ---- 响应式系统的依赖收集追踪原理

 为什么需要依赖收集? 

1、在 Vue 中,我们可能更新了不用更新视图的数据,如果没有依赖收集,则也会调用更新视图的 cb 函数,显然这是不合理的

2、Vue 页面中可能多处引用同一个 Vue 组件对象,更新响应式数据时,则应当更新多处视图,这些都涉及依赖收集 

首先的订阅者 Dep 类

/**
 * 依赖收集类
 */
class Dep {
    constructor() {
        /* 用来存放Watcher对象的数组 */
        this.subs = [];
    }

    /* 向subs数组中插入新的Watcher对象 */
    addSub(sub) {
        this.subs.push(sub);
    }

    /* 通知subs数组中所有Watcher对象更新视图 */
    /* 参数val是我为了模拟更新页面中数据设定的,本来没有,删掉即可 */
    notify(val) {
        this.subs.forEach(sub => sub.update(val));
    }
}

在订阅者Dep对象中 

  1. 用 addSub 方法可以在目前的 Dep 对象中增加一个 Watcher 的订阅操作;
  2. 用 notify 方法通知目前 Dep 对象的 subs 中的所有 Watcher 对象触发更新操作

然后的观察者Watcher

/**
 * 所有依赖观察者类
 */
class Watcher {
    /* id是我为了模拟更新页面数据设定的,删掉即可 */
    constructor(id) {
        /* 在new一个Watcher对象时将该对象赋值给Dep.target,在get中会用到 */
        Dep.target = this;
        this.id = id;
        console.log(Dep.target);
    }

    /* 视图更新方法 */
    /* val是我为了模拟更新页面数据设定的,删掉即可 */
    update(val) {
        console.log(this.id);
        document.querySelector(this.id).innerHTML = val;
        console.log("视图更新啦~");
    }
}

/* 用来指定当前创建的Watcher将其添加至依赖收集对象中 */
Dep.target = null;

 注意:响应式数据对象(data对象)中每个属性都有一个Dep对象用来收集依赖,而每个调用该属性的Vue组件都会创建一个新的Watcher对象,即都是一个新的依赖

最后的依赖收集

接下来我们更新一下上一篇博客中写到的 defineReactive 以及 Vue 的构造函数,来完成依赖收集

/**
 * 将属性响应式化函数
 * 
 * @param {object} obj 响应式化对象
 * @param {} key 响应式对象属性
 * @returns {} val 旧属性值
 * 
 */
function defineReactive(obj, key, val) {
    /* 每个属性都有一个dep对象 */
    const dep = new Dep();
    Object.defineProperty(obj, key, {
        enumerable: true,
        /* 可枚举 */
        configurable: true,
        /* 可配置修改或删除 */
        get: function reactiveGetter() { 
            /* 依赖采集 */
            dep.addSub(Dep.target);
            return val;
        },
        set: function reactiveSetter(newVal) {
            if (newVal === val) {
                return;
            }
            val = newVal;
            /* 更新属性值时通知所有观察者更新视图 */
            /* 参数是我为了模拟更新页面数据设定的,删掉即可 */
            dep.notify(val);
        }
    })
}
/**
 * Vue构造类
 */
class Vue {
    constructor(options) {
        this._data = options.data;
        observer(this._data);
        /* 新建一个Watcher观察者对象,这时候Dep.target会指向这个Watcher对象 */
        /* 参数是我为了模拟更新页面数据设定的,删掉即可 */
        new Watcher('.Test');
        /* 在这里模拟render的过程,为了触发test属性的get函数 */
        console.log('render~', this._data.test);
        /* 这里页面中存在两处调用该属性的Vue组件,则创建两个Watcher */
        new Watcher('.Vue');
        console.log('render~', this._data.test);
    }
}

完整代码 





    
    
    
    Vue源码解析



    
I am test Vue.
I am test Vue.

测试结果

Vue.js 内部运行机制 (二) ---- 响应式系统的依赖收集追踪原理_第1张图片

Vue.js 内部运行机制 (二) ---- 响应式系统的依赖收集追踪原理_第2张图片

总结

在遍历 data 属性设定 get 时,就会设定依赖收集方法, Dep对象即是依赖收集对象 ,所有的 Watcher 则是被收集依赖的观察者

在数据变化时, set 会调用 Dep 对象的 notify 方法通知它内部所有的 Watcher 对象进行视图更新。

Vue.js 内部运行机制 (二) ---- 响应式系统的依赖收集追踪原理_第3张图片图来源《Vue的内部运行机制》

参考文章

《Vue的内部运行机制》

代码参考《响应式系统的依赖收集追踪原理》

你可能感兴趣的:(JS,Vue,随记)