如何实时监听一个对象----模仿vue写一个对象的双向数据绑定

最近学习vue源码,在学习关于数据双向绑定的时候。看了好几遍,仍是有不少点不太理解。部门的大神建议本身按照思想模仿的写一个,体会会深不少。因而照作了,还真是神清气爽。这篇文章记录本身在写demo时碰见的思路和问题,往后回忆复习起来也方便。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,由于数据的属性值还有多是对象this

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

以上是vue中的作法。我本身实现demo的时候就是根据这个思路进行实现的。spa

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

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实例实现的关于对象的实时监听的小demo了。

最后,我想了想。为何vue要分这三个部分作呢?细想起来watcher主要的功能也就是set的时候的回调函数。若是没学习过vue的源码,应该就是直接把函数传入observer作回调函数就是了。这样watcher省了,dep也不用了。

可是又一想,同一个属性可能同时有几个watcher,就会要执行多个回调函数。那么就有了watcher和dep的必要了。

相关文章
相关标签/搜索