二、Vue 响应式系统的依赖收集追踪原理

为什么要依赖收集?

我们可以通过一些简单的例子来理解依赖收集的必要性。

栗子 🌰 1:视图不需要用到的数据更新

假设我们有一个 Vue 实例:

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

如果我们更新 text3

this.text3 = 'modify text3';

text3 没有出现在视图中,所以完全没有必要触发视图更新。依赖收集能避免这样的浪费——只更新视图中用到的变量!

栗子 🌰 2:多个实例依赖相同的全局对象

假设我们有一个全局对象,多个 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 的值:

globalObj.text1 = 'hello, text1';

因为 text1 被多个 Vue 实例依赖,Vue 的响应式系统需要确保这两个实例都更新它们的视图。这就需要“依赖收集”来记录哪些实例依赖于哪些数据,当数据变更时,通知它们更新。

实现依赖收集:订阅者 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:将 Watcher 添加到 subs 数组中,订阅当前数据的变化。
  • notify:在数据发生变化时调用,通知所有订阅者更新。

观察者 Watcher

Watcher 类用来表示一个依赖,它会记录数据被访问的位置,当数据更新时通知视图更新。

class Watcher {
    constructor () {
        /* 在实例化 Watcher 时,将其赋值给 Dep.target,用于依赖收集 */
        Dep.target = this;
    }

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

Dep.target = null;

Watcher 实例化时会将自己赋值给 Dep.target,让 Dep 记录当前依赖。

依赖收集实现

接下来我们修改 defineReactive 函数,将 DepWatcher 结合,实现依赖收集。

在闭包中增加 Dep 对象,用来收集 Watcher。当 get 被触发时,当前 Watcher 会被加入 Dep 的订阅队列。之后,set 会调用 Dep.notify(),通知所有的 Watcher 去更新视图。

function defineReactive(obj, key, val) {
    /* 创建一个 Dep 实例 */
    const dep = new Dep();

    Object.defineProperty(obj, key, {
        enumerable: true,
        configurable: true,
        get() {
            /* 将 Dep.target(当前 Watcher)添加到 dep 的 subs 中 */
            dep.addSub(Dep.target);
            return val;
        },
        set(newVal) {
            if (newVal !== val) {
                val = newVal;
                /* 触发 dep 的 notify 方法,通知所有 Watcher 更新视图 */
                dep.notify();
            }
        }
    });
}

class Vue {
    constructor(options) {
        this._data = options.data;
        observer(this._data);
        /* 创建一个 Watcher 实例,Dep.target 将指向这个 Watcher */
        new Watcher();
        /* 模拟 render 过程,触发 get 以便依赖收集 */
        console.log('render~', this._data.text1);
    }
}

在这个例子中,defineReactive 方法通过 Object.defineProperty 实现数据的 getset 拦截,并在 get 时记录依赖 Watcher。当数据变化时,触发 notify,通知所有 Watcher 更新视图。

小结

Vue 的响应式系统依赖于 依赖收集订阅更新

  1. 依赖收集:在 get 中记录当前依赖(Watcher),并将它存入 Depsubs 数组。
  2. 触发更新:当数据变化时,在 set 中调用 Depnotify,通知所有依赖更新视图。

通过这种机制,Vue 实现了高效、自动化的视图更新,使得响应式系统能够精准追踪数据变化与依赖关系,轻松完成视图更新。理解这些原理之后,Vue 响应式系统背后的“魔法”其实一点都不复杂!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

IT-墨痕

您的打赏是对我创作最大的肯定

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值