实现双向数据绑定

作者: lk儒家 | 来源:发表于2018-10-20 11:11 被阅读0次

    在上篇文章当中,我们实现了单向数据绑定,那么接下来,咱们一步一步来实现双向数据绑定。

    首先最大的问题就是我们如何去知道data里面的数据变化了,在Vue的实例化对象的data属性当中,可以查看到该属性上存在着一个get和set方法,而这两个方法实际上是通过Object.defineProperty()来实现数据劫持的。

    先来实现一个简单的需求,创建一个简单的对象。

    var Book={

        name:"明朝那些事"

    }

    但是现在我想要在获取到Book.name的时候有个书名号包裹着该怎么办呢?这就需要用到上文说到的Object.defineProperty()了。

    var Book={};

    var value="";

    Object.defineProperty(Book,"name",{ 

        get:function(){

            return value;

        },

        set:function(newValue){

            value="《"+newValue+"》";

        }

    });

    通过Object.defineProperty( )设置了对象Book的name属性,对其get和set进行重写操作,get就是在读取name属性这个值触发的函数,set就是在设置name属性这个值触发的函数。而在控制台输出的Book对象,乍一看,和Vue的数据长得有点类似,说明Vue确实是通过这种方法来实现数据的劫持的,那么我们就可以实现一个监听器observer了。

    /*

    * Description:监听器

    * obj:监听的对象

    */

    function observer(obj){

        if(!obj || typeof obj != "object"){

             return;

        }

        for(var key in obj){

             defineReactive(obj,key,obj[key]);

        }

    }

    function defineReactive(obj,key,value){

        //递归调用,监听该对象下面所有子对象的属性

        observer(value);

        Object.defineProperty(obj,key,{

            get:function(){

                return value;

            },

            set:function(newValue){

                value=newValue;

            }

        });

    }

    有了observer方法,我们就能够去实现监听Vue实例化对象的data属性值是否发生了更改,也就是说在Vue类当中调用observer方法即可,但是现在只是知道了哪个属性发生了变化,却无法实时的更新,要想实现这一步,我们先来考虑一个问题,当某一个属性值发生变化的时候,是需要将所有绑定该属性值的节点的值改为该属性值,所以这就用到了发布和订阅模式,也就是说,绑定同一个属性的每一个订阅者都存放在同一个订阅器内,如果该属性值变化了,订阅器会逐个的发布告知每一个订阅者。(更多详细信息请自行百度或参考lk儒家博客)

    根据理解,很容易的清楚的知道需要一个订阅者容器类,该类的实例化对象必须含有一个数组,然后有一个向该数组存放订阅者的方法,以及循环该数组通知所有订阅者更新的方法。

    //负责收集订阅者的容器

    function Dep(){

        this.subs=[];

    }

    Dep.prototype={

        //添加订阅者的方法

        addSub:function(sub){

            this.subs.push(sub);

        },

        //通知订阅者的方法

        notify:function(){

            this.subs.forEach(function(sub){

                //调用订阅者的update方法让其进行更新

                sub.update();

            });

        }

    }

    而对于订阅者,上文也进行分析了,也很清楚的知道每一个订阅者的实例上都存在着一个update方法,即更新其内容,而再仔细想一想,这些订阅者到底是什么,更新的是什么内容呢?是不是每一个绑定Vue的属性的节点都是一个订阅者,改变的是该节点的内容,也就是说,这些订阅者是在上篇文章当中compile的时候去创建,需要的参数值是Vue的实例化对象,绑定的该节点以及绑定的Vue的属性值。

    function Watcher(vm,node,name){

        this.vm=vm;

        this.node=node;

        this.name=name;

        //初始化

        this.update();

    }

    Watcher.prototype={

        //更新的方法,需要节点node,vue的实例化对象,属性名称

        update:function(){

            if(this.node.nodeType==1 && this.node.nodeName === 'INPUT'){

                this.node.value=this.vm.data[this.name];

                return;

            }

            this.node.nodeValue=this.vm.data[this.name];

        }

    }

    而现在我们剩下的问题就是observer和Watcher并没有关联到一起,细心的小伙伴可能已经发现了在Watcher类当中进行了一句初始化操作,而执行update方法为什么属于初始化呢?可以发现,在update方法当中是将this.vm.data[this.name]赋值给节点内容,而this.vm.data[this.name]早已经被监听了,即其会在赋值之前先执行该Vue实例化对象data属性的get方法,所以向订阅者容器当中添加订阅者的操作就是在监听的get方法当中,那么又该如何区分是否是需要添加的呢,这里可以在Watcher类当中添加一个标识,例如Dep.target,因为这个是全局的,所以在每一次操作完后就要对该值释放。

    修改后的Watcher类 修改后的defineReactive方法

    到此为止,整个Vue的双向绑定原理基本上说完了,但是运行发现,当修改输入框内容的时候,绑定同样属性值的内容根本就没有变化,这是由于没有给输入框绑定keyup事件,在该事件当中,只需要将输入框的内容赋值给Vue实例化对象的data中的绑定属性即可。

    给input输入框绑定keyup事件

    最后,奉上一张逻辑图,加深理解,biubiu!

    相关文章

      网友评论

        本文标题:实现双向数据绑定

        本文链接:https://www.haomeiwen.com/subject/wxnpzftx.html