Vue2.0双向绑定原理

398 阅读3分钟

一.写这篇的原因

自己在学Vue的时候一直觉得Vue的双向绑定很有趣,在看了《深入浅出Vue.js》这本书后算是对Vue双向绑定原理有了一个初步的认识,在学习过程中,由于水平有限,理解过程中有一些地方很挣扎,所以分享出来,希望对看到这篇文章的人有所帮助

二.读文章之前你需要了解

首先你需要使用过Vue,至少使用过Vue做过项目或者Demo。如果你不清楚Object.defineProperty的使用以及发布订阅模式的话,接下来我会尽可能的将清楚它的使用

三.数据劫持Object.defineProperty与发布订阅

Object.defineProperty(obj,prop,descriptor)的三个参数分别是
obj:被劫持的对象
prop:要定义或者修改属性的名称
descriptor:属性的描述符
详细的介绍可以参考MDN
虽然有缺陷,但是这个属性确实能侦测到一个对象的属性变化
以Vue文档上的例子作为参考

现在在Vue对象里面有一个data对象,我们希望能监听到data对象的message属性,那么我们可以写出这样的代码

let data={
    message:''
}

var val
Object.defineProperty(data,'message',{
    enumerable:true,  //可枚举
    configurable:true, //可配置
    get:()=>{
        return val;
    },
    set:(newVal)=>{
        if(newVal!==val) val=newVal
        console.log('message被改变')
    }
})
data.message='helloworld'

既然这样,我们可以对Object.defineProperty进行包装

function defineReactive(obj,key,val){
    Object.defineProperty(obj,key,{
        configurable:true,
        enumerable:true,
        get:function(){
            return val
        },
        set:function (newVal) {
            if(val===newVal) return
            val=newVal
        }
    })
}

通过对Object.defineProperty进行包装我们可以监听到对象属性的变化,但是这似乎并没有什么作用,所以还需要Dep类和Watcher实现发布订阅的设计模式来配合

Vue2.0中采用中的粒度大小,每一个组件对应一个Watcher,每一个组件也会对应一个虚拟Dom,而每一个组件创建后就会被收集在Dep类中。而Watcher是主动将自己添加到Dep中被收集的,这也是体现发布订阅思想的地方。具体看下面代码

//Dep类中,
addSub(sub){
    if(Dep.target){
        this.subs.push(sub)
    }
}

//Watcher类中
Dep.target=this

每生成一个Watcher实例,Watcher会将自己(this)设置到Dep.target中,从而触发Dep的逻辑从而让Dep收集自己。
总的来说,当组件创建后,会触发get从而被Dep中的数组收集,当数据改变的时候,会触发set通知到Watcher,Watcher通过虚拟Dom和diff算法来对比哪里有所改变,进而更改视图。
以上就是双向绑定的原理,读完是否能有所了解呢。觉得不错点个关注吧

下面是具体的各个类的方法,可以供给读者参考

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

//Dep类用于收集到哪些地方用到了Vue.data中的属性,
//实际上每一个使用到属性的地方都是一个Watcher类

class Dep {
    constructor() {
        this.subs=[];
    }
    addSub(sub){
        this.subs.push(sub)
    }
    removeSub(sub){
        remove(this.subs,sub)
    }
    depend(){
    //收集依赖
        if(Dep.target){
            this.addSub(Dep.target)
        }
    }
    
    //在实际上Dep类存的每一项都是一个Watcher的实例,所以能使用update方法
    //发布订阅的设计模式是在这一点上体现的
    //notify会让subs数组中存的watcher调用他的update方法使用回调函数来重新计算值
    notify(){
        //对subs数组进行深拷贝
        const subsCopy=this.subs.slice();
        for(let i=0;i<subsCopy.length;i++){
            subsCopy[i].update()
        }
    }
}

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

class Watcher{
    constructor(vm,exp,cb) {
        this.vm=vm
        this.getter=exp();
        this.cb=cb;
        this.value=this.get();
    }
    get(){
        Dep.target=this;
        const value=this.getter.call(this.vm,this.vm);
        Dep.target=undefined;
        return value
    }
    update(){
        const oldValue=this.value;
        this.value=this.get();
        this.cb.call(this.vm,this.value,oldValue)
    }
}

//以上只能侦测到一个属性,所以需要一个Observer类。Obersver类是将一个对象(obj)的所有子属性都能够被侦测到,代码逻辑比较简单,看一下就能够理解
class Obersver {
    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]])
        }
    }

}