手写vue响应式

48 阅读1分钟

Dep

export default class Depend {
    constructor() {
        this.subs = [];
    }

    addSub(sub) {
        console.log('addsub');
        this.subs.push(sub);
    }

    notify() {
        console.log('notify');
        console.log(this.subs);
        this.subs.forEach((e) => e.update());
    }
}

Observer

export default class Observer {
    constructor(value) {
        this.walk(value);
    }

    walk(value) {
        if (typeof value !== 'object' || !value) return;
        Object.keys(value).forEach((e) => this.defineReactive(value, e, value[e]));
    }

    defineReactive(data, key, val) {
        console.log(data, key, 'defineReactive');
        this.walk(val);

        const that = this;
        const dep = new Depend();
        Object.defineProperty(data, key, {
            get() {
                console.log('get', data, key);
                Depend.target && dep.addSub(Depend.target);
                return val;
            },
            set(newVal) {
                if (newVal === val) return;
                val = newVal;
                that.walk(newVal);
                dep.notify();
            }
        })
    }
}

Watcher

export default class Watcher {
    constructor(vm, key, cb) {
        this.vm = vm;
        this.key = key;
        this.cb = cb;
        Depend.target = this;
        this.oldValue = this.getter(vm.$data);
        Depend.target = null;
    }

    update() {
        console.log('update');
        const newVal = this.vm.$data[this.key];
        if (newVal === this.oldValue) return;
        this.cb(newVal);
    }

    getter(obj) {
        const newVal = {};
        const that = this;
        Object.keys(obj).forEach((e) => {
            if (typeof obj[e] === 'object' && obj[e] !== null) newVal[e] = that.getter(obj[e]);
            else newVal[e] = obj[e];
        });
        return newVal;
    }
}

Vue

export default class Vue {
    constructor(options) {
        this.$data = options.data || {};
        new Observer(this.$data);
        Object.keys(this.$data).forEach((k) => new Watcher(this, k, (newVal) => console.log('newVal --> ', newVal)));
    }
}

测试

const obj = {
    a: {
        m: {
            n: 5
        },
        b: 0
    },
    c: {
        d: {
            e: {
                f: 6666
            }
        }
    }
}

new Vue({
    data: {
        obj
    }
});

setTimeout(() => {
    obj.a.b = 1;
}, 2000);