什么是双向数据绑定?
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(); }); }};