Vue响应式之Object变化侦测原理

核心代码 

//Dep类用来收集依赖、删除依赖、通知依赖更新
class Dep {
    constructor() {
        this.subs = [];
    }
    addSub(sub) {
        this.subs.push(sub);
    }
    notify() {
        this.subs.forEach(sub => sub.update());
    }
    removeSub(sub) {
        const index = this.subs.indexOf(sub);
        if (index > -1) {
            this.subs.splice(index, 1);
        }
    }
}

// Observer类将data对象内的所有属性(包括子属性)都转换成getter/setter的形式,所以可侦测每个属性的变化
class Observer {
    constructor(data) {
        this.data = data;
        if (!Array.isArray(data)) {
            this.walk(data);
        }

    }
    walk(obj) {
        Object.keys(obj).forEach(key => {
            this.defineReactive(obj, key, obj[key]);
        });
    }
    defineReactive(data, key, val) {
        if (typeof val === 'object') { //递归子属性
            new Observer(val);
        }
        let dep = new Dep();
        Object.defineProperty(data, key, {
            enumerable: true,
            configurable: true,
            get() {
                dep.addSub(window.target);
                return val;
            },
            set(newValue) {
                if (newValue === val) {
                    return;
                }
                val = newValue;
                dep.notify();
            }
        });
    }
}

//Watcher实例即为上面所说的依赖,也就是我们常说的订阅者
class Watcher {
    constructor(vm, exp, cb) {
        this.vm = vm;  // ViewModel 的实例
        this.exp = exp;  // 要观察的数据属性的表达式(即字符串)
        this.cb = cb;  // 当数据属性变化时要调用的回调函数
        this.value = this.get();  // 初始化时获取数据属性的值
    }
    get() {
        window.target = this;  // 临时将当前 Watcher 实例设置为全局目标
        const value = this.vm[this.exp];  // 使用 exp 从 vm 中获取属性值(此时会触发getter)
        window.target = null;  // 清除全局目标
        return value;  // 返回获取到的属性值
    }
    update() {
        const value = this.get();//获取新值
        // 只有当新值和旧值不同时才调用回调函数并更新旧值
        if (value !== this.value) {
            this.value = value;
            this.cb.call(this.vm, value);
        }
    }
}

Watcher原理

先把自己设置到全局唯一的指定位置(如window.target),然后读取数据。因为读取了数据,所以会触发这个数据的getter。接着,在getter中就会从全局指定位置获取当前正在读取数据的Watcher实例,并把这个Watcher实例收集到Dep中。通过这样的方式,Watcher可以主动去订阅任意一个数据的变化。

总结

data通过Observer转换成了getter/setter的形式来追踪变化。

当外界通过watcher读取数据时,会触发getter从而将watcher实例(依赖)添加到该数据的专属Dep实例中。

当数据发生变化时,会触发该数据的setter,从而向Dep中的依赖发送通知。watcher接收到通知后,会向外界发送通知,变化通知到外界后可能会触发试图更新,也有可能触发用户的某个回调函数(调用Dep中的notify方法,遍历执行所有watcher实例上的update方法)。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值