Vue響應式原理-理解Observer、Dep、Watcher

蒼耳mtjj發表於2019-06-03

開篇

最近在學習Vue的原始碼,看了網上一些大神的部落格,看起來感覺還是蠻吃力的。自己記錄一下學習的理解,希望能夠達到簡單易懂,不看原始碼也能理解的效果?

如果有錯誤,懇求大佬們指點嘿?

Object.defineProperty

相信很多同學或多或少都瞭解Vue的響應式原理是通過Object.defineProperty實現的。被Object.defineProperty繫結過的物件,會變成「響應式」化。也就是改變這個物件的時候會觸發get和set事件。進而觸發一些檢視更新。舉個例子?

function defineReactive (obj, key, val) {
    Object.defineProperty(obj, key, {
        enumerable: true,
        configurable: true,
        get: () => {
            console.log('我被讀了,我要不要做點什麼好?');
            return val;
        },
        set: newVal => {
            if (val === newVal) {
                return;
            }
            val = newVal;
            console.log("資料被改變了,我要把新的值渲染到頁面上去!");
        }
    })
}

let data = {
    text: 'hello world',
};

// 對data上的text屬性進行繫結
defineReactive(data, 'text', data.text);

console.log(data.text); // 控制檯輸出 <我被讀了,我要不要做點什麼好?>
data.text = 'hello Vue'; // 控制檯輸出 <hello Vue && 資料被改變了,我要把新的值渲染到頁面上去!>
複製程式碼

Observer 「響應式」

Vue中用Observer類來管理上述響應式化Object.defineProperty的過程。我們可以用如下程式碼來描述,將this.data也就是我們在Vue程式碼中定義的data屬性全部進行「響應式」繫結。

class Observer {
    constructor() {
        // 響應式繫結資料通過方法
    	observe(this.data);
    }
}

export function observe (data) {
    const keys = Object.keys(data);
    for (let i = 0; i < keys.length; i++) {
       // 將data中我們定義的每個屬性進行響應式繫結
       defineReactive(obj, keys[i]);
    }
}
複製程式碼

Dep 「依賴管理」

什麼是依賴?

相信沒有看過原始碼或者剛接觸Dep這個詞的同學都會比較懵。那Dep究竟是用來做什麼的呢? 我們通過defineReactive方法將data中的資料進行響應式後,雖然可以監聽到資料的變化了,那我們怎麼處理通知檢視就更新呢?

Dep就是幫我們收集【究竟要通知到哪裡的】。比如下面的程式碼案例,我們發現,雖然data中有textmessage屬性,但是隻有message被渲染到頁面上,至於text無論怎麼變化都影響不到檢視的展示,因此我們僅僅對message進行收集即可,可以避免一些無用的工作。

那這個時候messageDep就收集到了一個依賴,這個依賴就是用來管理datamessage變化的。

<div>
    <p>{{message}}</p>
</div>
複製程式碼
data: {
    text: 'hello world',
    message: 'hello vue',
}
複製程式碼

當使用watch屬性時,也就是開發者自定義的監聽某個data中屬性的變化。比如監聽message的變化,message變化時我們就要通知到watch這個鉤子,讓它去執行回撥函式。

這個時候messageDep就收集到了兩個依賴,第二個依賴就是用來管理watchmessage變化的。

watch: {
    message: function (val, oldVal) {
        console.log('new: %s, old: %s', val, oldVal)
    },
}        
複製程式碼

當開發者自定義computed計算屬性時,如下messageT屬性,是依賴message的變化的。因此message變化時我們也要通知到computed,讓它去執行回撥函式。 這個時候messageDep就收集到了三個依賴,這個依賴就是用來管理computedmessage變化的。

computed: {
    messageT() {
        return this.message + '!';
    }
}
複製程式碼

圖示如下:一個屬性可能有多個依賴,每個響應式資料都有一個Dep來管理它的依賴。

依賴收集

如何收集依賴

我們如何知道data中的某個屬性被使用了,答案就是Object.defineProperty,因為讀取某個屬性就會觸發get方法。可以將程式碼進行如下改造:

function defineReactive (obj, key, val) {
    let Dep; // 依賴

    Object.defineProperty(obj, key, {
        enumerable: true,
        configurable: true,
        get: () => {
            console.log('我被讀了,我要不要做點什麼好?');
            // 被讀取了,將這個依賴收集起來
            Dep.depend(); // 本次新增
            return val;
        },
        set: newVal => {
            if (val === newVal) {
                return;
            }
            val = newVal;
            // 被改變了,通知依賴去更新
            Dep.notify(); // 本次新增
            console.log("資料被改變了,我要把新的值渲染到頁面上去!");
        }
    })
}
複製程式碼

什麼是依賴

那所謂的依賴究竟是什麼呢?上面的圖中已經暴露了答案,就是Watcher

Watcher 「中介」

Watcher就是類似中介的角色,比如message就有三個中介,當message變化,就通知這三個中介,他們就去執行各自需要做的變化。

Watcher能夠控制自己屬於哪個,是data中的屬性的還是watch,或者是computedWatcher自己有統一的更新入口,只要你通知它,就會執行對應的更新方法。

因此我們可以推測出,Watcher必須要有的2個方法。一個就是通知變化,另一個就是被收集起來到Dep中去。

class Watcher {
    addDep() {
        // 我這個Watcher要被塞到Dep裡去了~~
    },
    update() {
        // Dep通知我更新呢~~
    }, 
}
複製程式碼

總結

回顧一下,Vue響應式原理的核心就是ObserverDepWatcher

Observer中進行響應式的繫結,在資料被讀的時候,觸發get方法,執行Dep來收集依賴,也就是收集Watcher

在資料被改的時候,觸發set方法,通過對應的所有依賴(Watcher),去執行更新。比如watchcomputed就執行開發者自定義的回撥方法。

本篇文章屬於入門篇,能夠先簡單的理解ObserverDepWatcher三者的作用和關係。後面會逐漸詳細和深入,循序漸進的理解和學習。

如果你覺得對你有幫助,就點個贊吧~

正在書寫的系列~

Github部落格 歡迎交流~

相關文章