美文网首页Vue.js专区前端开发笔记
手动实现一个MVVM框架(下)

手动实现一个MVVM框架(下)

作者: 不净莲华 | 来源:发表于2019-01-25 12:57 被阅读2次

    实现双向绑定

    HTML部分

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

    使用方式

    var app = new Svm({
        el:'#app',
        data: {
            number: 0
        },
        methods: {
            increment: function(){
                this.number ++;
            }
        }
    });
    

    详解

    构造函数

    //构造函数
    function Svm (options) {
      this._init(options);
    }
    
    

    _init方法的定义

    Svm.prototype._init = function(options){
      this.$options = options;
      this.$el = document.querySelector(options.el);
      this.$data = options.data;
    
      // 绑定的指令对象
      this._binding = {}; 
      // 监听数据变化 改变视图
      this._observe(this.$data);
      // 解析指令 
      this._compile(this.$el);
    };
    

    实现_observer函数

    Svm.prototype._observer = function(obj){
      //遍历数据对象
      for(let key in obj) {
        if(obj.hasOwnProperty(key)) {
          //指令合集
          this._binding[key] = {
            _directives: []
          };
    
          let value = obj[key];
        }
    
        //递归调用
        if(typeof value === 'object') {
          this._observer(value);
        }
    
        let binding = this._binding[key];
        Object.defineProperty(this.$data, key, {
          enumerable: true,
          configurable: true,
          get: function () {
            return value
          },
    
          set: function (val) {
            if(val !== value) {
              // 修改数据
              value = val;
              binding._directives.forEach(function (item) {
                item.update(); 
              })
            }
          }
        })
    
      }
    
    };
    

    接下来实现_compile函数

    Svm.prototype._compile = function(root){
      var _this = this;
      var nodes = root.children;
    
      // 遍历传入的dom
      for(var i = 0; i < nodes.length; i++) {
        var node = nodes[i];
        //递归遍历
        if(node.children.length) {
          this._compile(node)
        }
    
        //对元素上的属性做判断
    
        //s-click
        if(node.hasAttribute('s-click')) {
          node.onclick = (function () {
            let attrVal = node.getAttribute('s-click');
            return _this.$methods[attrVal].bind(_this.$data)//返回对应方法绑定到对应的node上
          })()
        }
    
        //s-model
    
        if(node.hasAttribute('s-model') && node.tagName === 'INPUT' || node.tagName === 'TEXTAREA') {
          node.addEventListener('input', (function (i) {
            let attrVal = node.getAttribute('s-model');
            _this._binding[attrVal]._directives.push(new Watcher(
              'input',
              node,
              _this,
              attrVal,
              'value'
            ));
    
            return function (e) {
              //修改data数据
              _this.$data[attrVal] = node[i].value;
            }
          })(i))
          
        }
    
        //s-bind
    
        if(node.hasAttribute('s-bind')) {
          let attrVal = node.getAttribute('s-bind');
          _this._binding[attrVal]._directives.push(new Watcher(
            'text',
            node,
            _this,
            attrVal,
            'innerHTML'
          ))
        }
      }
    };
    

    Watcher构造函数

    /**
     * [Watcher 监听数据更新DOM]
     * @param {[type]} name [指令名称]
     * @param {[type]} el   [指令对应的DOM元素]
     * @param {[type]} vm   [所属的Svm实例]
     * @param {[type]} exp  [data中的属性]
     * @param {[type]} attr [绑定的属性值]
     */
    
     function Watcher (name, el, vm, exp, attr) {
        this.name = name;
        this.el = el; 
        this.vm = vm;
        this.exp = exp; 
        this.attr = attr; 
        this.update(); 
     }
    
     //更新数据
     Watcher.prototype.update = function(){
       this.el[this.attr] = this.vm.$data[this.exp]
     };
    

    走一下其中的逻辑

    执行_init方法把options各个选项挂载至实例上,_observer方法遍历data所有属性
    并设置set存取器属性当data中的属性被修改时将执行 Watcher.update 方法设置视图中的数据与实例中的数据同步_compile函数遍历被挂载的dom并检测s-click,s-model,s-bind属性,若存在就往_binging中添加一个key为绑定数据的key的_directives数组中一watcher 若是s-model首先通过检测input事件执行改变data中的数据,然后添加一个watcher执行其update方法改变el上的数据而改变data中的数据则会执行update方法改变el数据,若是s-bind,则是直接添加一个watcher改变data触发update方法改变视图, s-click则是将methods中的方法挂到dom的onclick上并把data传入


    结语

    仔细理清其中的逻辑思路便会更加清晰vue背后的原理对于框架也有了更深的理解

    相关文章

      网友评论

        本文标题:手动实现一个MVVM框架(下)

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