vue响应式系统的依赖收集追踪原理

vue响应式系统的依赖收集追踪原理

最近在学习《剖析 Vue.js 内部运行机制》这本小册,本篇文章就“vue响应式系统的依赖收集追踪原理”这一小节,写一写自己的理解+注释,对小册进行二次输出。

为什么要依赖收集?

在上一篇 “vue响应式系统的基本原理”文章中,我们写了如下代码,对数据进行响应式管理。
其中在set函数里面,当数据有更新,我们就直接调用了cb函数更新视图,这样有些许不妥。小册作者用了两个例子来说明。
在这里插入图片描述

例子1:视图上不需要用的数据更新了,显然调用cb函数更新视图是不正确的

我们现在有这么一个 Vue 对象。

new Vue({
    template: 
        `<div>
            <span>{{text1}}</span> 
            <span>{{text2}}</span> 
        <div>`,
    data: {
        text1: 'text1',
        text2: 'text2',
        text3: 'text3'
    }
});

然后我们做了这么一个操作。

this.text3 = 'modify text3';

我们修改了 data 中 text3 的数据,但是因为视图中并不需要用到 text3 ,所以我们并不需要触发上一章所讲的 cb 函数来更新视图,调用 cb 显然是不正确的。

例子2:当多个视图用到的数据被更新时,只是简单调用cb也是不正确的,要通知多个视图更新

假设我们现在有一个全局的对象,我们可能会在多个 Vue 对象中用到它进行展示。

let globalObj = {
    text1: 'text1'
};

let o1 = new Vue({
    template:
        `<div>
            <span>{{text1}}</span> 
        <div>`,
    data: globalObj
});

let o2 = new Vue({
    template:
        `<div>
            <span>{{text1}}</span> 
        <div>`,
    data: globalObj
});

这个时候,我们执行了如下操作。

globalObj.text1 = 'hello,text1';

我们应该需要通知 o1 以及 o2 两个vm实例进行视图的更新,「依赖收集」会让 text1 这个数据知道“哦~有两个地方依赖我的数据,我变化的时候需要通知它们~”。

最终会形成数据与视图的一种对应关系,如下图。
在这里插入图片描述

依赖收集是如何实现的?

这里也是引用小册作者的实现,加上本人对此的分析理解。
依赖收集,字面意思其实就是让数据的每个属性都能 收集到依赖它的事物

怎么理解?

  • 每个数据在被更新的时候,是不是都要通知一下依赖它的视图,告诉它我更新了,你也需要更新了,这样才能够做到响应式嘛?这跟上一章我们的思想是相似的。
  • 在上一章我们是 数据一旦更新,我们就去更新视图,而没有区分 是通知依赖它的视图,所以导致的不妥。
  • 那么要实现 去通知依赖它的视图,我们是不是得先收集一下有哪些是依赖它的,然后当它变化的话,就去通知它收集到的依赖视图更新,对吗?
  • 综上所述,简单来说,就是先收集“依赖视图”,等改变时,再通知“依赖视图”更新。

订阅者 Dep

我们首先定义一个订阅者 Dep,它里面有容器可以存放 收集到的依赖(术语一点叫 Watcher 观察者对象)。

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

    /* 在subs中添加一个Watcher对象 */
    addSub (sub) {
        this.subs.push(sub);
    }

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

为了便于理解我们只实现了添加的部分代码,主要是两件事情:

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

观察者 Watcher

class Watcher {
    constructor () {
        /* 在new一个Watcher对象时将该对象赋值给Dep.target,在get中会用到 */
        Dep.target = this;
    }

    /* 更新视图的方法 */
    update () {
        console.log("视图更新啦~");
    }
}

Dep.target = null;

依赖收集
接下来我们修改一下 defineReactive 以及 Vue 的构造函数,来完成依赖收集。

我们在闭包中增加了一个 Dep 类的对象,用来收集 Watcher 对象。在对象被「读」的时候,会触发 reactiveGetter 函数把当前的 Watcher 对象(存放在 Dep.target 中)收集到 Dep 类中去。之后如果当该对象被「写」的时候,则会触发 reactiveSetter 方法,通知 Dep 类调用 notify 来触发所有 Watcher 对象的 update 方法更新对应视图。

function defineReactive (obj, key, val) {
    /* 一个Dep类对象 */
    const dep = new Dep();
    
    Object.defineProperty(obj, key, {
        enumerable: true,
        configurable: true,
        get: function reactiveGetter () {
            /* 将Dep.target(即当前的Watcher对象存入dep的subs中) */
            dep.addSub(Dep.target);
            return val;         
        },
        set: function reactiveSetter (newVal) {
            if (newVal === val) return;
            /* 在set的时候触发dep的notify来通知所有的Watcher对象更新视图 */
            dep.notify();
        }
    });
}

class Vue {
    constructor(options) {
        this._data = options.data;
        observer(this._data);
        /* 新建一个Watcher观察者对象,这时候Dep.target会指向这个Watcher对象 */
        new Watcher();
        /* 在这里模拟render的过程,为了触发test属性的get函数 */
        console.log('render~', this._data.test);
    }
}

总结一下:
为什么上述依赖收集,能解决我们之前的问题呢?

  1. 首先 针对第一个例子,当我们更新了一个视图上没有的数据时,它也会调用它的set函数,但是当它去遍历 通知它收集的依赖时(也就是subs),是空的,当然也就不会去更新视图了。
  2. 针对第二个例子,当有多个视图依赖这个更新的数据时,它会调用它的set函数,就会去遍历 通知收集到的依赖,有多少依赖视图,就去执行对应视图的更新方法来更新视图。
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值