超簡單的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,會出現性能問題。

相關文章
相關標籤/搜索