vue变化侦测

1.什么是watcher

watcher是一个中介的角色,数据发生变化时通知它,然后它在通知其他地方。
关于watcher,看一个使用它的例子:

//keypath
vm.$watch('a,b,c', function(newVal, oldVal){
	//do something
}
)

这段代码表示data.a.b.c属性发生变化时,触发第二个参数中的函数。
那么如何实现这个功能呢?只要把这个watcher实例添加到data.a.b.c属性的Dep中就行了。然后,当data.a.b.c的值发生变化时,通知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);
    }
}

在get方法中先把window.target设置成了this,也就是当前的watcher实例,然后再读一下data.a.b.c的值,这会触发getter。
触发getter就会触发收集依赖的逻辑。而关于收集依赖,会从window.target中读取一个依赖并添加到Dep中。
这就导致,只要先在window.target附一个this,然后再读一下值去触发getter就可以把this主动添加到keypath的Dep中。
依赖注入到Dep中后,每当data.a.b.c的值发生变化时,就会让依赖列表中所有的依赖循环触发update方法,也就是watcher中的update方法。而update方法会执行参数中的回调函数,将value和oldValue传到参数中。
所以,其实不管是用户执行的vm.$watch(‘a.b.c’, (value, oldValue) => {}),还是模板中用到的data,都是通过watcher来通知自己是否需要发生变化。
那么parsePath是怎么读取一个字符串的keypath的,下面来解释一下:

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

可以看到,先将keypath用.分割成数组,然后循环数组一层一层去读数据,最后拿到的obj就是keyPath中想要读的数据。

2.递归侦测所有key

现在,其实已经可以实现变化侦测的功能了,但是前面介绍的代码只能侦测数据中的某一个属性,我们希望把数据中的所有属性(包括子属性)都侦测到,所以要封装一个Observer类。这个类的作用是将一个数据内的所有属性(包括子属性)都转换成getter/setter的形式,然后去追踪它们的变化:

export class Observer{
    constructor(value) {
        this.value = value;
        if (!Array.isArray(value)){
            this.walk(value)
        }
    }
    
    
    walk(obj){
        const keys = Object.keys(obj);
        for (let i = 0; i < keys.length; i++){
            defineReactive(obj, keys[i], obj[keys[i]])
        }
    }
}


function defineReactive(data, key, val) {
    if (typeof val == 'object'){
        new Object(val);
    }
    let dep = new Dep();
    Object.defineProperty(data, key, {
        enumerable: true,
        configurable: true,
        get: function(){
            dep.depend();
            return val;
        },
        set: function(newVal){
            if (val == newVal){
                return;
            }
            val = newVal;
            dep.notify();
        }
    })

}

在上面的代码中,我们定义了Observer类,它用来将一个正常的object转换成被侦测的object。
然后判断数据的类型,只有object类型的数据才会调用walk将每一个属性转换成getter/setter的形式来侦测变化。
最后,在defineReavtive中新增new Observer(val)来递归子属性,这样我们就可以把data中的所有属性(包括子属性)都转换成getter/setter 来侦测变化。

3.关于Object的问题

前面介绍了Object类型数据的变化侦测原理,了解了数据的变化是通过getter/setter来追踪的。也正是由于这种追踪方式,有些语法中即便是数据发生了变化,vue.is也追踪不到。
比如,向Object添加属性:

var vm = new Vue({
    el: "#el",
    template: "#demo-tenplate",
    methods: {
        action(){
            this.obj.name = "berwin";
        }
    },
    data: {
        obj: {}
    }
})

在action方法中,我们在obj上面新增了name属性,vue无法侦测到这个变化,所以不会向依赖发送通知。
在比如,从obj中删除一个属性:

var vm = new Vue({
    el: "#el",
    template: "#demo-tenplate",
    methods: {
        action(){
            delete this.obj.name;
        }
    },
    data: {
        name: 'berwin'
    }
})

在上面的代码中,我们在action方法中删除了obj中的name属性,而vue无法侦测到这个变化,所以不会向依赖发送通知。
vue通过Object.defineProperty来将对象的key转换成getter/setter的形式来追踪变化,但getter/setter只能追踪一个数据是否被修改,无法追踪新增属性和删除属性,所以才会导致上面的例子中提到的问题。

4.总结

变化侦测就是侦测数据的变化,当数据吧发生变化时,要能侦测到并发出通知。
Object可以通过Object.defineProperty将属性转换成getter/setter的形式来追踪变化。读取数据时会触发getter,修改数据时会触发setter。
我们需要在getter中收集哪些依赖使用了数据,当setter被触发时,去通知getter中收集的依赖数据发生了变化。
收集依赖需要为依赖找一个依赖的地方,为此我们创建了Dep,它用来收集依赖、删除依赖和向依赖发送消息等。
所谓的依赖,其实及时watcher,只有watcher触发的getter才会收集依赖,那个watcher触发了getter,就把哪个watcher收集到Dep中。当数据发生变化时,会循环依赖列表,把所有的watcher都通知一遍。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值