网站地图    收藏   

主页 > 前端 > javascript >

vue双向绑定实现原理详解

来源:未知    时间:2020-03-15 15:22 作者:小飞侠 阅读:

[导读] 今天带来vue双向绑定实现原理详解。 vue的双向数据绑定是区分普通对象和数组的。数组的比较复杂,下篇再介绍。今天介绍vue对于对象数据的双向绑定。vue是通过数据劫持的方式来实现...

今天带来vue双向绑定实现原理详解。

vue的双向数据绑定是区分普通对象和数组的。数组的比较复杂,下篇再介绍。今天介绍vue对于对象数据的双向绑定。vue是通过数据劫持的方式来实现双向数据绑定的。数据劫持的核心就是object.defineProperty().简单介绍下这个方法。这个方法是es5定义的,经过该方法定义的对象属性会变成访问器属性。以下是一个简单的例子:


function Observer(obj,key,value){
    if(Object.prototype.toString.call(value)=='[object Object]'){
        Object.keys(value).forEach(function(key){
            arguments.callee(value,key,value[key]);
        })
    }
    Object.defineProperty(obj,key,{
        enumerable:true,
        configurable:true,
        get:function(){
        },
        set:function(){
        }
    })
}


访问器属性的最大特点便是内部可以指定get、set方法。在对属性进行值访问的时候会调用定义的get方法,对属性进行赋值的时候会调用set方法。


接着说双向数据绑定。双向数据绑定分为以下三个部分:


Observer:负责数据劫持,把所有的属性转换成访问器属性,达到对数据进行观测的目的。需要对数据进行递归观测,因为数据的属性值还有可能是对象


Watcher:数据的观察者,在数据发生变化之后执行的相应的回调函数,需要对数据进行递归watch,因为数据的属性值还有可能是对象


Dep(Dependency):顾名思义,是Observer和Watcher的连接。如何连接呢?每一个observer会创建一个Dep实例,实例在get数据的时候为数据收集watcher,在set的时候执行watcher内的回调方法。


以上是vue中的做法。我自己实现demo的时候就是根据这个思路进行实现的。


先是Observer,递归将属性设置为访问器属性,代码如下:


function Observer(obj,key,value){
    if(Object.prototype.toString.call(value)=='[object Object]'){
        Object.keys(value).forEach(function(key){
           new arguments.callee(value,key,value[key]);
        })
    }
    Object.defineProperty(obj,key,{
        enumerable:true,
        configurable:true,
        get:function(){
            return value;
        },
        set:function(newVal){
            if(value===newVal)return;
        value = newVal;
        }
    })
}


先判断属性值value是不是对象,如果是,还需要对对象进行递归调用,观测数据


接着是Watcher,目的在于在数据发生变化的时候执行相应的回调函数



function Watcher(data,k,v,fn){
    if(Object.prototype.toString.call(data)==='[object Object]'){
        Object.keys(v).forEach(function(key){
            new arguments.callee(v,key,v[key],fn);
        })
    }
    this.fn = fn;
    data[k];
}

也是先判断是不是对象,是的话递归调用观察属性值。如何让watcher和observer产生联系呢?


observer中对所有的属性设置成了访问器属性,所以如果我们在watcher中调用属性,求属性的值就会调用到属性的get方法。


既然observer和watcher可以在get方法内产生连接,那么是不是可以在get的时候收集不同的watcher,然后在set函数呗调用的时候执行这些watcher中的方法。这样就需要在observer中引入一个对象,在get函数内收集watcher,在set函数内遍历执行watcher的回调方法,已达到动态响应的目的。


这个对象就是Dep。每个observer内都会实例化一个Dep对象,用于收集watcher和用于执行watcher,根据这个思路,可以得到以下的代码:


function Dep(){
    var sub=[];
    this.addSub=function(watcher){
        this.sub.push(watcher);
    };
    this.notify=function(){
        this.sub.forEach(function(watcher){
            watcher.fn();
        })
    }
}


根据思路,Dep当中需要一个存储watcher的数据结构,从添加和遍历的角度选择,数组比较合适。然后是需要一个添加watcher的方法,在就是需要一个遍历watcher的方法。进而,得出了以上的代码。


 现在三个组件都已经有了,那他们之间怎么协调工作呢?按照之前的思路,和我们现在有的代码。在observer中加入Dep收集和执行依赖的代码。就发现,存在怎么在get中得到watcher的实例的问题。既然Dep本身作为watcher和observer的连接桥梁,那这个事情就让Dep做吧。此时需要做的事情是,需要一个变量,在Watcher中收集watcher实例,在get中将Watcher实例放入Dep实例的数组中,以便于set中使用。


思考这个变量的功能,他不能出现在构造函数和原型链中,这样watcher的变化会实时的提现在每个实例上。那么只有在构造函数本身这个函数对象定义这个变量比较合适了。函数对象上的变量不会通过new操作符影响到所有实例,又能完成存储watcher的功能。


先在watcher中收集,那么watcher中的代码如下:


function Watcher(data,k,v,fn){
    if(Object.prototype.toString.call(data)==='[object Object]'){
        Object.keys(v).forEach(function(key){
            new arguments.callee(v,key,v[key],fn);
        })
    }
    this.fn = fn;
    Dep.target = this;//
    data[k];
    Dep.target=null;//
}


在watcher调用属性求值之前,将watcher保存到Dep.target变量中,在求值之后(get中Dep实例收集了之后)将该值置为null,这样就不会在别的属性求值的时候影响到别的属性。


已经在Watcher中收集到了watcher实例,那么observer中如何使用呢。看如下代码:


function Observer(obj,key,value){
    var dep = new Dep();
    if(Object.prototype.toString.call(value)=='[object Object]'){
        Object.keys(value).forEach(function(key){
           new arguments.callee(value,key,value[key]);
        })
    }
    Object.defineProperty(obj,key,{
        enumerable:true,
        configurable:true,
        get:function(){
            if(Dep.target){//存储依赖
                dep.addSub(Dep.target);//
            }//
            return value;
        },
        set:function(newVal){
            if(value===newVal)return;
         value = newVal;
            dep.notify();//执行依赖
        }
    })
}

          


到这一步,我们基本上对象的双向绑定已经完成了。所有的功能都已经实现了。


在我自己运行调试的时候发现一个问题。如果set的值又是一个对象,那么对象的属性改变将无法得到监控。所以,在set中加上以上代码就完整了:


if(Object.prototype.toString.call(value) ==='[object Object]'){
    Object.keys(value).forEach(function(key){
         new Observer(value,key,value[key]);
         new Watcher(value,key,value[key],function(v,key){
            console.log('你修改了数据');
            // document.getElementById('dd').innerHTML=v.key;
      });
   })
}

为什么vue要分这三个部分做呢?其实watcher主要的功能也就是set的时候的回调函数。

那么如果没学习过vue的源码,应该就是直接把函数传入observer做回调函数就是了。这样watcher省了,dep也不用了。

以上就是vue双向绑定实现原理详解全部内容,感谢大家支持自学php网。

自学PHP网专注网站建设学习,PHP程序学习,平面设计学习,以及操作系统学习

京ICP备14009008号-1@版权所有www.zixuephp.com

网站声明:本站所有视频,教程都由网友上传,站长收集和分享给大家学习使用,如由牵扯版权问题请联系站长邮箱904561283@qq.com

添加评论