超简单的Vue响应式系统原理解释

所谓Vue.js的响应式系统也就是指,在修改数据模型的时候,视图会自动变化,而用户不须要像命令式编程那样再手动去操做视图的变化。用高大上的说法就是:使用订阅者模式,达成声明式编程的目的javascript

Vue.js的响应式系统很是适合用订阅者模式的缘由就在于,每次在修改数据的时候,对应通知到使用到该数据的组件,就实现了响应式系统。java

那么Vue.js 是如何实如今每次数据改变的时候,自动通知到对应的组件的呢?react

带着这个问题,咱们来看下面的代码演示编程

代码只是拿来作展现原理的,真实的源代码复杂的多,可是原理类似数组

首先咱们来定义两个类:闭包

class Dep {
    constructor () {
        this.subs = [];
    }
    addSub (sub) {
        this.subs.push(sub);
    }
    notify () {
        this.subs.forEach((sub) => {
            sub.update();
        })
    }
}

class Watcher {
    constructor () {
        Dep.target = this;
    }
    update () {
        console.log("视图更新啦~");
    }
}
复制代码

Dep 类是拿来存放订阅者的容器,它里面有个subs数组拿来存放有哪些订阅者,addSub(sub)就是添加对应的订阅者。notify()则是通知全部的订阅者,数据变化了。函数

Watcher 类是订阅者类(又叫观察者,二者是一样的意思)。里面的 update() 函数就是拿来作对应的视图更新,具体的在这里不展开。至于 Dep.target = this; 这行代码到后面再解释。性能

咱们再来看看 Vue.js 是如何将数据模型变得响应的,ui

function defineReactive (obj, key, val) {
    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();
        }
    });
}
复制代码

这里应用到了原生 JavaScriptObject.defineProperty() 函数,若是对于这块不熟悉的,参考这里this

defineReactive()函数返回的时候,会生成一个闭包,而这个闭包里面只有 dep 这一个属性,而且该闭包只能被 obj[key] 属性的 getset 方法所使用。dep 就是拿来存放 obj[key] 这个数据所对应的订阅者的。

你会发现,咱们添加订阅者的操做就是在 get 方法中完成的,而为何添加的订阅者是 Dep.target, 这点在后面解释。

set 函数就是拿来修改数据,而且使用 dep.notify(); 通知对应的订阅者。

这个函数是对让 Vue 实例中的 data 中的一个属性变得响应的,那么固然咱们须要将 data 中的全部属性都变得响应式。这时候,就须要用到 observer() 函数了。

function observer (value) {
    if (!value || (typeof value !== 'object')) {
        return;
    }
    Object.keys(value).forEach((key) => {
        defineReactive(value, key, value[key]);
    });
}
复制代码

observer() 这个函数很简单,接受对应的 data, 并将里面的属性都变得响应式。

好了,前面的基础都打好了,如今咱们来看看 Vue 是如何在构造函数中,将整个响应式系统打造好的,其实也很是简单:

class Vue {
    constructor(options) {
        this._data = options.data;
        observer(this._data);
        new Watcher();
        console.log('render~', this._data.test);
    }
}
let o = new Vue({
    data: {
        test: "I am test."
    }
});
复制代码

咱们先执行对应的observer()函数,将 data 里面的属性都变得响应式。

而后再生成一个 Watcher() 对象,这里就能够解释前面留下的问题,也就是为何 Watcher 类的构造函数里面要执行 Dep.target = this; 的操做,也就是将 Dep.target 指向在当前 Vue实例中生成的 Watcher 对象;而咱们留下的第二个问题,也就是为何 dep.addSub(Dep.target); 添加的订阅者是 Dep.target, 其实也就是当前 Vue 实例中生成的 Watcher 对象。

可是这时 data 中的每一个属性的订阅者其实尚未添加,因此咱们要跑一次 render function 来获取一次数据,也就是调用 data 每一个属性的 get 方法。console.log('render~', this._data.test); 是拿来模拟 render function 的调用的。

这里还有一个问题,那就是,若是 render function 被调用了屡次,那么订阅者就会被添加屡次,因此在最后还须要执行如下操做:

Dep.target = null;
复制代码

你会发现,咱们这里是对每一个 Vue 实例(或者 Vue 组件)使用一个 Watcher 对象,这是Vue2.0的特性;而在Vue1.0 中是针对每一个标签实现一个Watcher,数据过大时,就会有不少个watcher,会出现性能问题。

相关文章
相关标签/搜索