vue双向数据绑定核心原理基本模拟实现

vue双向数据绑定主要是通过 Object对象的defineProperty属性,重写data的set和get函数来实现的,这里只会实现最基本的内容,主要实现v-model,v-bind 和v-click三个命令,其他命令也可以自行补充

<!DOCTYPE html>
<html>
<head>
  <meta charset="utf-8" />
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <title>myVue双向数据绑定</title>
  <meta name="viewport" content="width=device-width, initial-scale=1">

</head>
<body>
  <div id="app">
    <form>
      <input type="text" v-model="number" />
      <button type="button" v-click="increment">增加</button>
    </form>
    <h3 v-bind="number"></h3>
  </div>

  <script>
    window.onload=function(){
      // 挂载并创建根实例
      var app=new myVue({
        el: '#app',
        data: {
          number: 0,
        },
        methods: {
          increment: function(){
            this.number++;
          }
        }
      });
    }

    // 定义myVue构造函数
    function myVue(options){
      // 自定义一个_init函数,为了初始化这个构造函数
      this._init(options);
    }

    // 定义_init初始化函数
    myVue.prototype._init=function (options){
      this.$options=options;
      this.$el=document.querySelector(options.el);
      this.$data=options.data;
      this.$methods=options.methods;

      // 保存着view与model的映射关系,
      this._binding={};

      // 调用_observe函数对data进行处理
      this._observe(this.$data);

      // 用来解析指令(v-model,v-click,v-bind等指令),在过程中并对view和model进行绑定
      this._compile(this.$el);
    }

    // 实现_observe函数
    myVue.prototype._observe=function(obj){
      var value;
      for(var key in obj){
        if(obj.hasOwnProperty(key)){
          // 按照此例子,此处应该为 _binding:{ number: _directives:[] }
          this._binding[key]={
            _directives: [] //data里的对象对应的映射的指令集(v-model,v-click,c-bind等等)经过Watch类处理过后的结果集
          }
          value=obj[key]; //获取对象的属性值
          if(typeof value === 'object'){  //如果对象值还是一个对象,则进行遍历处理
            this._observe(value);
          }

          var binding=this._binding[key]; //定义变量binding来缓存遍历到的对象对应的指令集

          Object.defineProperty(obj,key,{
            enumerable: true,
            configurable: true,
            get: function(){
              console.log("获取value: "+value);
              return value;
            },
            set: function(newVal){
              console.log("更新newVal: "+newVal,"之前的值: "+value);
              if(newVal !== value){
                value=newVal;
                binding._directives.forEach(function(item){   //此例子就是当number改变时,触发update函数,更新binding[number]._directives中绑定的Watch类
                  item.update(); //调用update函数更新
                });
              }
            }
          });
        }
      }
    }

    // 实现_compile函数
    myVue.prototype._compile=function(root){
      var _this=this;
      var nodes=root.children;
      for(var i=0;i<nodes.length;i++){
        var node=nodes[i];
        if(node.children.length){ //对所有元素进行遍历操作
          this._compile(node);
        }

        if(node.hasAttribute('v-click')){   // 如果有v-click属性,我们监听它的onclick事件,触发increment事件,即number++
          node.onclick=(function(){
            var attrVal=nodes[i].getAttribute('v-click');
            return _this.$methods[attrVal].bind(_this.$data);   //bind是使data的作用域与method函数的作用域保持一致
          })()
        }

        if(node.hasAttribute('v-model') && (node.tagName == 'INPUT' || node.tagName =='TEXTAREA')){
          node.addEventListener('input',(function(key){
            //_this._binding['number']._directives = [一个Watch实例]         
            // 其中Watch.prototype.update = function () {         
            //    node['vaule'] = _this.$data['number'];  这就将node的值保持与number一致      
            // }
            var attrVal=node.getAttribute('v-model');
            _this._binding[attrVal]._directives.push(new watch(
              'input',
              node,
              _this,
              attrVal,
              'value'
            ))
            return function(){
              _this.$data[attrVal]=nodes[key].value; //使number的值与node的value保持一致,已经实现了数据双向绑定
            }
          })(i));
        }

        if(node.hasAttribute('v-bind')){  //如果有v-bind,只需要及时将v-bind中的值更新为data里的值即可
          var attrVal=node.getAttribute('v-bind');
          _this._binding[attrVal]._directives.push(new watch(
            'text',
            node,
            _this,
            attrVal,
            'innerHTML'
          ));
        }
      }
    }

    // 定义一个watch类
    function watch(name,el,vm,exp,attr){
      this.name=name;       //指令名称,文本节点则对应text,
      this.el=el;           //指令对应的dom元素
      this.vm=vm;           //所属myVue实例
      this.exp=exp;         //指令对应的值,如“number”
      this.attr=attr;       //指令对应的属性值,如“innerHTML”

      this.update();        //同步更新操作
    }

    watch.prototype.update=function(){
      this.el[this.attr]=this.vm.$data[this.exp]; //比如h3.innerHTML = this.data.number; 当number改变时,会触发这个update函数,保证对应的DOM内容进行了更新
    }

  </script>
</body>
</html>
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值