依赖收集

在 getter 中收集依赖,在 setter 中触发依赖

fucntion defineReactive(data, key, val) {
    const dep = [] //新增
    Object.defineProperty(data, key {
        enumerable: true,
        configurable: true,
        get: fucntion() {
            dep.push(window.target) //新增
            return val;
        },
        set: function(newVal) {
            if(val === newVal) {
                return ;
            }

            for(let i = 0;i < dep.length; i++) {
                dep[i](newVal, val);
            }
            val = newVal;
        }
    })
}

这里我们新增了数组dep,用来存储被收集的依赖。
然后在set被触发时,循环dep以触发收集到的依赖

更好的解耦版本

把依赖收集的代码封装成一个Dep类,它专门帮助我们使用这个类,我们可以收集依赖,删除依赖或者向依赖发送通知等。

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

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

    removeSub(sub) {
       remove(this.subs, sub);
    }

    depend () {
        if(window.target) {
            this.addSub(window.target);
        }
    }

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

function remove (arr, item) {
    if(arr.length) {
        const index = arr.indexOf(item);
        if(index > -1) {
            return arr.splice(index, 1);
        }
    }
}

function defineReactive (data, key, val) {
    const dep = new Dep();
    Object.defineProperty(data, key, {
        enumerable: true,
        configurable: true,
        get: function () {
            dep.depend();
            return val;
        },
        set: function () {
            if(val === newVal) {
                return;
            }
            val = newVal;
            dep.notify(); //新增
        }
    })
}

依赖收集到哪了?收集到Dep中了。

Watcher

watcher是一个中介角色,数据发生变化时通知它,然后它再通知到其他地方。

export default class watcher {
    constructor (vm, expOrFn, cb) {
        this.vm = vm;
        // 执行this.getter(), 就可以读取data.a.b.c的内容
        this.getter = parsePath(expOrFn);
        this.cb = cb;
        this.value = this.get();
    }

    get() {
        window.target = this;
        let value = this.getter.call(this.vm, this.vm);
        window.target = undefined;
        return value;
    }

    update () {
        const oldValue = this.value;
        this.value = this.get();
        this.cb.call(this.vm. this.value, oldValue);
    }
}

先在window.target赋一个this, 也就是当前watcher实例,然后再读一下 data.a.b.c的值,这肯定会触发getter。
触发了getter, 就会触发依赖收集逻辑,按上文介绍的,会从window.target中读取一个依赖并添加到Dep中。
这就导致,只要先在window.target中赋一个this,然后再读一下值,去触发getter,就可以把this主动加到keypath的Dep中。

parsePath 实现也很机智

/**
* 解析简单路径
**/

const bailRE = /[^\w.$]/;
export function parsePath (path) {
    if(bailRE.test(psth)) {
        return ;
    }
    const segments = path.split('.');
    return function (obj) {
        for(let i = 0,len = segments.length;i<len;i++) {
            if(!obj) return ;
            obj = obj[segments[i]];
        }
        return obj;
    }
}