1. 程式人生 > 實用技巧 >Vue2.x是怎麼收集依賴的

Vue2.x是怎麼收集依賴的

概述

說到 vue 的響應式原理,我們都能很快答出資料劫持和釋出者訂閱者模式,通過 Object.defineProperty 來劫持 getter 和 setter,在 getter 的時候訂閱依賴,在 setter 的時候釋出響應執行依賴,從而達到響應式的目的。

但是如果深入一點,它是怎麼收集、釋出、管理依賴的呢?或者說,原始碼裡面的 defineReactive、Dep、Watcher 之間有什麼樣的關係呢?

我通過自己寫了一個簡易的響應式系統弄懂了這些,記錄下來,供以後工作時參考,相信對其他人也有用。

小型的響應系統

為了回答上面的問題,我打算寫一個簡易的的響應式系統,為了簡便,這個系統只考慮沒有巢狀的物件,程式碼如下:

function defineReactive(obj, key, val) {
    const dep = new Dep();

    Object.defineProperty(obj, key, {
        enumerable: true,
        configurable: true,
        get() {
            if (Dep.target) {
                dep.depend();
            }
            return val;
        },
        set(newVal) {
            val = newVal;
            dep.notify();
        }
    });
}

class Dep {
    constructor() {
        this.subs = [];
    }

    addSub(sub) {
        this.subs.push(sub);
    }

    removeSub() {
        const index = this.subs.indexOf(sub);
        if (index > -1) {
            this.subs.splice(index, 1);
        }
    }

    depend() {
        if (Dep.target) {
            Dep.target.addDep(this);
        }
    }

    notify() {
        const subs = this.subs.slice();
        for (let i = 0, l = subs.length; i < l; i++) {
            subs[i].update();
        }
    }
}

Dep.target = null;

class Watcher {
    constructor(cb) {
        this.getter = cb;
        this.deps = [];
        this.value = this.get();
    }

    get() {
        Dep.target = this;
        const value = this.getter();
        console.log('Dep.target', Dep.target);
        return value;
    }

    addDep(dep) {
        dep.addSub(this);
    }

    update() {
        const value = this.get();
    }
}

const obj = {};

defineReactive(obj, 'text', 'Hello World!');

const watcher = new Watcher(() => {
    document.querySelector('body').innerHTML = obj.text;
});

把上面的程式碼複製到瀏覽器的控制檯執行,就可以看到瀏覽器裡面出現了Hello World,然後我們繼續在控制檯輸入obj.text = 'Define Reactive',可以看到瀏覽器裡面的Hello World就變成了Define Reactive

結合 Vue 的生命週期

我們把上面的例子帶入 Vue 的生命週期裡面看:

1.首先我們知道,在beforeCreate階段,Vue會進行各種初始化,比如事件、生命週期等,其實就等效於這段程式碼:

const obj = {};

2.在created階段,Vue會初始化狀態:data、compute、props、methods等,那其實就等效於這段程式碼:

defineReactive(obj, 'text', 'Hello World!');

3.在beforeMount階段,Vue會對 vm 建立一個 watcher,當變動的時候,使用 update 進行更新,這就等效於這段程式碼:

const watcher = new Watcher(() => {
    document.querySelector('body').innerHTML = obj.text;
});

總結

1.必須先用defineReactive設定為響應式的,然後才能在watcher裡面實現依賴收集,並且例項化多個watcher都能被收集到。

2.總的來說,每一個 dep 例項其實就是一個訂閱中心,它通過閉包存在,然後在通過 defineReactive 把資料轉變為響應式之後,此時的資料沒有任何變化,但是一旦例項化了一個 watcher,資料就會自動把這個 watcher 新增到自己的訂閱中心,當改變這個資料的時候,也會自動釋出通知,達到更新訂閱的 watcher 的目的。

3.vue2.x的原始碼的響應式系統除了上面的程式碼,還做了很多別的工作,比如觀測巢狀物件、觀測陣列、處理已經存在的getter和setter、對 watcher 進行排程等等。