天天看点

手动实现Vue的数据双向绑定v-model

手动实现Vue的数据双向绑定v-model

    所需构造函数:myVue和Watcher

    myVue中有 _init

<!DOCTYPE html>
<html >
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <meta http-equiv="X-UA-Compatible" content="ie=edge">
  <title>Document</title>
</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>
</body>
<script>
  //  2.需要一个myVue的构造函数
  function myVue(options){
    this._init(options)
  }
  myVue.prototype = {
    //  3.为了初始化这个构造函数,为其添加一个_init属性
    _init: function(options) {
      this.$options = options //  options为上面使用时传入的结构体,包括el,data,methods
      this.$el = document.querySelector(options.el) //  el是#app,this.$el是id为app的element元素
      this.$data = options.data //  this.$data = {number:0}
      this.$methods = options.methods //  this.$methods = {increment: function(){}}
        
       // 6._binding保存着model与view的映射关系,也就是我们前面定义的
      //Watcher的实例。当model改变时,我们会触发其中的指令类更新,保证view也能实时更新
       this._binding = {};   

      this._observe(this.$data)
      //  7.那么如何将view与model进行绑定呢?接下来我们定义一个_complie函数,来解析我们的指令
      //(v-bind, v-model, v-click)等,并在这个过程中对view与model进行绑定
      this._complie(this.$el) 
    },
    //  4.接下来实现_observe函数,对data进行处理,重写set和get函数,并改造_init函数
    _observe: function(obj){  //  {number:0}
      var value;
      for(var key in obj){
        if(obj.hasOwnProperty(key)){  //  key为obj的实例属性
          // 6.
          this._binding[key] = {  // 按照前面的数据,
            _binding = {number: _directives: []} 
            _directives: []
          }
          var binding = this._binding[key]
          
          value = obj[key];
          //  如果value 仍为引用类型,则需要重新遍历
          if(typeof value === 'object'){
            this._observe(value)
          }
          Object.defineProperty(this.$data, key, {  //  关键
            enumerable: true, //  可枚举
            configurable: true, //  可修改
            get(){
              console.log(`获取${value}`)
              return value
            },
            set(newValue){
              console.log(`更新${newValue}`)
              if(value !== newValue) {
                value = newValue
              }
              // 6.
              // 当number改变时,触发_binding[number]._directives 中的绑定的Watcher类的更新
              binding._directives.forEach(function (item) {  
              console.log(item)
                item.update();
              })
            }
          })
        }
      }
    },
    _complie: function(root) {  //root为id为app的element元素,也就是根元素
    var _this = this
    var nodes = root.children
    for(var i = 0; i< nodes.length; i++) { 
      var node = nodes[i]
      if(node.children.length) {// 对所有元素进行遍历,并进行处理
        this._complie(node)
      }

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

      // 如果有v-model属性,并且元素是INPUT或者TEXTAREA,我们监听它的input事件
      if (node.hasAttribute('v-model') && (node.tagName == 'INPUT' || node.tagName == 'TEXTAREA')) { 
        node.addEventListener('input', (function(key) {  
          console.log('触发input')
          var attrVal = node.getAttribute('v-model');
           //_this._binding['number']._directives = [一个Watcher实例]
           // 其中Watcher.prototype.update = function () {
           // node['vaule'] = _this.$data['number'];  这就将node的值保持与number一致
           // }
          _this._binding[attrVal]._directives.push(new Watcher(  
            'input',
            node,
            _this,
            attrVal,
            'value'
          ))

          return function() {
            // 使number 的值与 node的value保持一致,已经实现了双向绑定
            _this.$data[attrVal] =  nodes[key].value; 
          }
        })(i));
      } 

      // 如果有v-bind属性,我们只要使node的值及时更新为data中number的值即可
      if (node.hasAttribute('v-bind')) { 
        var attrVal = node.getAttribute('v-bind');
        console.log(attrVal)
        _this._binding[attrVal]._directives.push(new Watcher(
          'text',
          node,
          _this,
          attrVal,
          'innerHTML'
        ))
      }
    }

    }
  }

  //  5.接下来要写一个指令类Watcher,用来绑定更新函数,实现对dom元素的更新
    function Watcher(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()
    }
    Watcher.prototype = {
      update() {
//比如 H3.innerHTML = this.data.number; 当number改变时,会触发这个update函数,保证对应的DOM内容进行了更新。
        this.el[this.attr] = this.vm.$data[this.exp]                       
        input.value = this.data.number
      }
    }

  //  1.模范vue中双向数据绑定的实现方式
  window.onload = function() {
    var app = new myVue({
    el: '#app',
    data: {
      number: 0
    },
    methods: {
      increment() {
        this.number ++
      }
    }
  })
  }
  
</script>
</html>
           

继续阅读