vue.js的双向绑定,我们用原生js来实现

Posted 夕水

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了vue.js的双向绑定,我们用原生js来实现相关的知识,希望对你有一定的参考价值。

前面讲到原型的时候,说过vue.js框架处理数据的核心原理,实际上就是通过Object.defineProperty()方法将访问器函数getter和setter函数重写.接下来,我也来根据这个原理,使用原生js实现vue.js的数据双向绑定.


使用vue.js写过数据的双向绑定就应该知道页面的结构应该是怎么样的,不过vue.js在绑定数据的时候用的是模板引擎语法,这里暂时不写这个,就用指令代替即可.如下:

html:

    <div id="#app-1">

            <input type="text" v-model="testData">

            <p v-bind="testData"></p>

    </div>

首先,需要定义一个构造函数.代码如下:

//参数为构造函数需要配置的属性,因为vue.js就是通过构造函数创建一个vue实例的.

function eveningWater(option){

    //初始化该构造函数的基础属性也就是实例传入的对象

    this.init(option);

}

//将初始化函数添加到构造函数的原型上

eveningWater.prototype.init = function(option){

    this.option = option;

    //重写构造实例属性

    this.$el = document.querySelector(option.el);

    this.$data = option.data;

    //定义一个对象,用于保存自定义指令属性

    this.binding = {};

    //观察对象的变化

    this.observe(this.$data);

    //观察dom元素,响应页面视图变化

    this.readElement(this.$el);

}


eveningwater.prototype.observe = function(dataobj){

    //定义一个变量保存数据的属性名

    var value;//如构造函数实例的data对象中为test,则为test,这里是testData

    //遍历data对象

    for(var key in dataobj){

        //判断如果属性名存在于对象实例中

        if(dataobj.hasOwnProperty(key)){

            //自定义指令用一个数组来保存

            this.binding[key] = {

                    directivesArr:[]

            };

            value = dataobj[key];

            //判断如果value里面数据还是对象则通过递归原理再往里面遍历

            if(typeof value === 'object' && value !== null){

                this.observe(value);

            };

            //新定义变量赋值为自定义指令保存对象,以方便之后访问

            var _binding = this.binding[key];

            //利用Object.defineProperty()重写访问器函数

            Object.defineProperty(dataobj,key,{

                    enumberable:true,//属性特性,表示允许循环遍历

                    configurable:true,//属性特性,表示可以通过delete删除属性

                    get:function(){

                            return value;

                    },//get访问器函数,读取数据

                    set:function(newVal){

                        如果数据不一致,则调用数据更新函数

                        if(value !== newVal){

                                value = newVal;

                                _binding.directivesArr.forEach(function(dir){

                                        dir.update();

                                })

                        }

                    }

            })

        }

    }

}

//读取dom元素

eveningWater.prototype.readElement = function(root){

        //this对象指向构造函数初始化的属性,即前面的this.option对象

        var _self = this;

        //获取根元素的子元素,刚好是一个数组

        var nodes = root.children;//[input,p]

        for(var i = 0,len = nodes.length;i < len;i++){

                var node = nodes[i];

                //判断如果根元素的子元素还有嵌套的子元素,则通过递归原理继续遍历

            if(node.children.length){

                this.readElement(node);

            }

            //开始判断含有指令的元素

            if(node.hasAttriBute('v-mode') && node.tagName.toLocaleCase() === 'input'){

        //取得指令属性

        var attrModel = node.getAttribute('v-mode');

         //输入值事件监听,注意这里要用函数表达式形成一个块级作用域,因为要修改前面的data对象里的值

        node.addEventListener('input',(function(key){

//这里是一个难点,由于我们把数据值都添加到一个监听函数中去 ,传入的参数有标签名,标签dom元素,构造实例函数的属性对象,获取的指令属性,与input标签值获取的value              

 _self.binding.directivesArr.push(new WatchListener(node.tagName.toLocaleCase(),node,_self,attrmodel,'value'));

        //当值改变时,就改变构造函数实例中的data对象中的对象属性值,即testData的值,并且这里要返回一个函数,这是一个回调函数

        return  function(){

                _self.$data[attrmodel] = nodes[key].value;

        }

        })(i))

}

//开始判断包含有v-bind指令的标签元素

            if(node.hasAttribute('v-bind')){

                    var attrBind = node.getAttribute('v-bind');

                    _self.binding.directivesArr.push(new WatchListener(node.tagName.toLocaleCase(),node,_self,attrBind,'textContent'));

             }

        }

}

//监听函数,参数标签名,dom元素,构造函数实例对象,指令属性,以及对应的值改变属性

function WatchListener(name,el,vm,data,attr){

        this.name = name;

        this.el = el;

        this.vm = vm;

        this.data = data;

        this.attr = attr;

        this.update();

}

//数据更新时函数

WatchListener.prototype.update = function(){

        //注意this对象指向watchListener构造函数

        this.el[this.attr] = this.vm.$data[this.data];

}

//页面加载时运行

window.onload = function(){

    //初始化构造函数

    new eveningWater({

        el:'#app-1',

        data:{

            testData:'测试数据'

        }

    })

}


//至此,一个简单的数据双向绑定示例就完成了,完整效果如以下动图显示:



以上是关于vue.js的双向绑定,我们用原生js来实现的主要内容,如果未能解决你的问题,请参考以下文章

Vue.js快速上手|单向绑定与双向绑定

如何创建从 vue.js 实例到自定义原生 Web 组件的双向绑定?

Vue.js实现双向绑定的原理

Vue.js双向绑定的实现原理

Vue.js双向绑定的实现原理

Vue.js双向绑定的实现原理