乐趣区

关于vue.js:vue双向数据绑定

什么是双向数据绑定?

Vue 是一个 MVVM 框架,数据绑定简略来说,就是当数据发生变化时,相应的视图会进行更新,当视图更新时,数据也会跟着变动。

Vue.js 则是通过数据劫持以及联合发布者 - 订阅者来实现的,数据劫持是利用 ES5 的 Object.defineProperty(obj, key, val) 来劫持各个属性的的 setter 以及 getter,在数据变动时公布音讯给订阅者,从而触发相应的回调来更新视图。

1. 实现最根底的数据绑定

 <div id="app">
        <input type="text" v-model="inp">
        输出的值为:{{inp}}
        <div>
            <input type="text" v-model="inp">
        </div>
    </div>
    <script>
        var vm = new MVue({
            el: '#app',
            data: {inp: 'hello world'}
        })
    </script>

实现思路:

1、输入框以及文本节点和 data 中的数据进行绑定

2、输入框内容变动时,data 中的对应数据同步变动,即 view => model

3、data 中数据变动时,对应的文本节点内容同步变动 即 model => view

双向数据绑定原理图

2、实现 Compile

compile 次要做的事件是解析模板指令,将模板中的变量替换成数据,而后初始化渲染页面视图,并将每个指令对应的节点绑定更新函数,增加监听数据的订阅者,一旦数据有变动,收到告诉,更新视图,如图所示:

function compile(node, vm) {let reg = /\{\{(.*)\}\}/;
  // 元素节点
  if (node.nodeType === 1) {
    var attrs = node.attributes;
    for (let attr of attrs) {if (attr.nodeName === 'v-model') {
        // 获取 v -model 指令绑定的 data 属性
        var name = attr.nodeValue;
        // 绑定事件
        node.addEventListener('input', function(e) {vm.$data[name] = e.target.value;
        });
        // 初始化数据绑定
        node.value = vm.$data[name];
        // 移除 v -model 属性
        node.removeAttribute('v-model');
      }
    }
  }

  // 文本节点
  if (node.nodeType === 3) {if (reg.test(node.nodeValue)) {var name = RegExp.$1 && RegExp.$1.trim();
      // 绑定数据到文本节点中
      node.nodeValue = node.nodeValue.replace(new RegExp('\\{\\{\\s*(' + name + ')\\s*\\}\\}'),
        vm.$data[name]
      );
    }
  }
}

批改下 MVue 构造函数,减少模板编译:

function MVue(options) {
  this.$el = options.el;
  this.$data = options.data;

  // 模板编译
  let elem = document.querySelector(this.$el);
  elem.appendChild(nodeToFragment(elem, this));
}

3. 实现 watcher

function Watcher(vm, node, name, type) {
  Dep.target = this;
  this.name = name;
  this.node = node;
  this.vm = vm;
  this.type = type;
  this.update();
  Dep.target = null;
}

Watcher.prototype = {update: function() {this.get();
    this.node[this.type] = this.value; // 订阅者执行相应操作
  },
  // 获取 data 的属性值
  get: function() {console.log(1);
    this.value = this.vm[this.name]; // 触发相应属性的 get
  }
};

4. 实现 Dep 来为每个属性增加订阅者

function Dep() {this.subs = [];
}
Dep.prototype = {addSub: function(sub) {this.subs.push(sub);
  },
  notify: function() {this.subs.forEach(function(sub) {sub.update();
    });
  }
};
退出移动版