vue源碼學習記錄--數據響應觀察者模式的實現之Dep、Watcher、Observer之間的關係

Dep、Watcher、Observer主要解決兩個問題javascript

  1. 如何實現依賴的收集?
  2. 依賴發生變化時如何通知頁面更新?

三者之間的關係:java

data中的每個屬性對應一個dep,dep中的subs保存了依賴該屬性的watcher,每個watcher有一個update方法,該方法會在依賴的屬性的值發生變化的時候調用。
一個對象或數組對應一個__ob__屬性即observer實例。由observer(data)開始,在observer中執行new Observer(value)操做,new Observer中遍歷value對象,爲每個值defineReactive,在defineReactive中默認會對觀測的data對象進行深度觀測,即會遞歸觀測屬性值,若是屬性值時對象或數組的話。此時的watcher只有一個,即爲渲染watcher,全部的dep都會添加到該watcher的deps中。react

綜上,三者之間的關係爲一個渲染watcher對應一個組件,該watcher中的deps保存了data中全部屬性值的dep,wacther中保存deps的目的是爲了依賴取消時移除dep實例中subs記錄的watcher,每一個dep實例有一個subs屬性,該屬性用於保存依賴產生dep實例的data屬性的watcher,dep由observer產生,一個對象或數組對應一個observer實例,每個屬性對應一個dep。數組

隨便寫點代碼來幫助理解:app

/** data */
    const data = {
        name: 'nick',
        age: 25,
        job: 'Front-end Developer'
    };

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

        Object.defineProperty(obj, key, {
            enumerable: false,
            configurable: true,
            get: function reactiveGetter() {
                console.log(`${key}使用到了,須要收集起來`);
                if (Dep.target) {
                    dep.depend();
                }
                return val;
            },
            set: function reactiveSetter(newVal) {
                if (newVal === val) {
                    return;
                }
                console.log(`${key}被設置了,須要通知依賴它的觀察者更新哦`);
                val = newVal;
                dep.notify();
            }
        })
    }

    function def(obj, key, val, enumerable) {
        Object.defineProperty(obj, key, {
            value: val,
            enumerable: !!enumerable,
            configurable: true,
            writable: true
        })
    }

    function isObject(value) {
        return Object.prototype.toString.call(value) === '[object Object]';
    }

    function createElement(tag) {
        return document.createElement(tag);
    }


    /** observer */
    class Observer{
        constructor(value) {
            def(value, '__ob__', this);
            this.walk(value);
        }

        walk(obj) {
            const keys = Object.keys(obj);
            let l = keys.length, i = 0;
            for (; i < l; i++) {
                defineReactive(obj, keys[i], obj[keys[i]])
            }
        }
    }

    function observer(obj) {
        if (!isObject(obj)) {
            return;
        }
        let ob = obj.__ob__;
        if (ob) {
            console.log('對象已經被觀測過了');
            return ob;
        }
        ob = new Observer(obj);
        return ob;
    }

    /** watcher */
    class Watcher {
        constructor(fn) {
            this.deps = [];
            this.getter = fn;
            this.get();
        }
        get() {
            pushStack(this);
            this.getter(data);
            popStack();
        }
        addDep(dep) {
            this.deps.push(dep);
            dep.addSub(this);
        }
        update() {
            this.getter(data);
        }
    }

    /** dep */
    class Dep {
        static target;
        constructor() {
            this.subs = [];
        }
        addSub(sub) {
            this.subs.push(sub);
        }
        notify() {
            for (let i = 0; i < this.subs.length; i++) {
                this.subs[i].update();
            }
        }
        depend() {
            if (Dep.target) {
                Dep.target.addDep(this);
            }
        }
    }
    Dep.target = null;

    const targetStack = [];
    function pushStack(target) {
        targetStack.push(target);
        Dep.target = target;
    }
    function popStack() {
        targetStack.pop();
        Dep.target = targetStack[targetStack.length - 1];
    }

    /** render */
    observer(data);
    const watcher = new Watcher(_render);
    console.log(watcher);

    function _render(data) {
        const _c = createElement;
        const p1 = _c('p');
        p1.textContent = data.name;
        const p2 = _c('p');
        p2.textContent = String(data.age);
        const div = _c('div');
        div.setAttribute('id', 'app');
        div.appendChild(p1);
        div.appendChild(p2);

        const oldDiv = document.getElementById('app');
        console.log(oldDiv);

        if (oldDiv) {
            document.body.replaceChild(div, oldDiv);
        }
        else {
            document.body.appendChild(div);
        }

    }
複製代碼
相關文章
相關標籤/搜索