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

31 篇文章 0 订阅
6 篇文章 1 订阅

 为什么需要依赖收集? 

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);
    }
}

完整代码 

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Vue源码解析</title>
</head>

<body>
    <div class="Test">I am test Vue.</div>
    <div class="Vue">I am test Vue.</div>
</body>
<script>
    /**
     * 依赖收集类
     */
    class Dep {
        constructor() {
            /* 用来存放Watcher对象的数组 */
            this.subs = [];
        }

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

        /* 通知subs数组中所有Watcher对象更新视图 */
        notify(val) {
            this.subs.forEach(sub => sub.update(val));
        }
    }

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

        /* 视图更新方法 */
        update(val) {
            console.log(this.id);
            document.querySelector(this.id).innerHTML = val;
            console.log("视图更新啦~");
        }
    }

    /**
     * 将属性响应式化函数
     * 
     * @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);
            }
        })
    }

    /**
     * 将对象所有属性响应式化函数
     * 
     * @param {object} value 响应式化对象
     * 
     */
    function observer(value) {
        if (!value || (typeof value !== 'object')) {
            return;
        }
        /* 遍历value中属性定制set和get */
        Object.keys(value).forEach(key => defineReactive(value, key, value[key]));
    }
    /**
     * 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);
        }
    }
    var test = new Vue({
        data: {
            test: 'I am test.'
        }
    });
    Dep.target = null;
</script>

</html>

测试结果

总结

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

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

图来源《Vue的内部运行机制》

参考文章

《Vue的内部运行机制》

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

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值