原理说明
- 通过建立虚拟dom树document.createDocumentFragment(),方法创建虚拟dom树。
- 一旦被监测的数据改变,会通过Object.defineProperty定义的数据拦截,截取到数据的变化。
截取到的数据变化,从而通过订阅——发布者模式,触发Watcher(观察者),从而改变虚拟dom的中的具体数据。
- 最后,通过更新虚拟dom的元素值,从而改变最后渲染dom树的值,完成双向绑定
Vue的模式是m-v-vm模式,即(model-view-modelView),通过modelView作为中间层(即vm的实例),进行
双向数据的绑定与变化。
而实现这种双向绑定的关键就在于:
Object.defineProperty和订阅—发布者模式这两点。
最终效果
HTML代码
<div id="app"><input type="text" v-model="text">{{text}}</div>
JS代码
/*** 第一步:创建一个observe去监听vue的实例vm中data的所有属性第二步:调用nodeToFragment()①==劫持==id=‘app’的元素下的所有子节点。(后面对于DOM的操作都在这里进行操作,最后更新完成再更新到我们真正的DOM树上,避免对DOM的反复操作)②在这里调用解析器compile第三步:compile()①遍历dom中节点是否有v指令属性,有的话就对视图进行==初始化==(如:把上面的text:‘hello world’初始化到<input type="text" v-model="text">的value中)②为所有有v指令的节点==创建一个watcher==,并把对应的动态绑定属性值传过去compile中与Observer的联系:③在初始化的过程中,就必定会调用到我们observe的get方法,因此==在Observe这里把watcher添加到订阅器Dep中==④在compile中我们还会设置对事件的监听(如input事件),执行了对视图中的数据修改->反映到model数据中(调用observer的set方法)第四步:Observer通知订阅器Dep,数据值已修改。Dep.notify()通知所有的watcher去更新视图*///监视者/*** [observe description]* @param {[type]} obj [vue的data]* @param {[type]} vm [vue实例]* @return {[type]} [description]*/function observe(obj, vm) {Object.keys(obj).forEach(function(key) {defineReactive(vm, key, obj[key]);})};/*** [defineReactive description]* @param {[type]} obj [vue实例]* @param {[type]} key [属性名]* @param {[type]} val [属性值]* @return {[type]} [description]*/function defineReactive(obj, key, val) {console.log('observe:defineReactive');var dep = new Dep();console.log(dep);//obj:vue实例vm(被操作的目标对象),key要定义or要修改的属性名Object.defineProperty(obj, key, {get: function() {console.log('gettttttttt!');console.log(Dep.target);if (Dep.target)dep.addSub(Dep.target)return val;},set: function(newVal) {console.log('set!!!!!!!!!!!!!!!!');if (newVal === val) return;val = newVal;console.log(val);dep.notify();}});};//解析器//node:劫持的dom, vm:vue实例function compile(node, vm) {console.log('compile-----start');var reg = /\{\{(.*)\}\}/;if (node.nodeType === 1) //元素,查找v-modal绑定的属性值{var attr = node.attributes;for (var i = 0; i < attr.length; i++) {if (attr[i].nodeName == 'v-model') {var name = attr[i].nodeValue; //v-model的属性名node.addEventListener('input', function(e) {console.log('检测到值改变:' + e.target.value);vm[name] = e.target.value; //给相应的 data 属性赋值,进而触发该属性的 set 方法});node.value = vm[name]; //把vue对应属性名的值赋给节点,触发get方法,vm.data[name]不会触发get方法,但是vm[name]会触发get方法!!node.removeAttribute('v-model'); //为什么要删除v-model?}}new Watcher(vm, node, name, 'input'); //通知watcher去修改?}if (node.nodeType === 3) {if (reg.test(node.nodeValue)) {var name = RegExp.$1; // 获取匹配到的字符串name = name.trim();new Watcher(vm, node, name, 'text');}}console.log('compile-----end');}//订阅器function Dep() {this.subs = [];console.log('dep');}Dep.prototype = {addSub: function(sub) {console.log('addToDep')this.subs.push(sub);},notify: function() {this.subs.forEach(function(sub) {sub.update(); //通知watch去update});}}//var vdom = nodeToFragment(document.getElementById('app'));//console.log(vdom);function nodeToFragment(node, vm) {console.log('nodeToFragment---start');var flag = document.createDocumentFragment();var child;//console.log(node.childNodes); // length : 5while (child = node.firstChild) {compile(child, vm); //把劫持的child给compile解析flag.appendChild(child);}//console.log(node.childNodes); // length : 0console.log('nodeToFragment------end')return flag;}/*** [Watcher 订阅者]* @param {[type]} vm [vue实例对象]* @param {[type]} node [含有v指令的节点]* @param {[type]} name [description]* @param {[type]} nodeType [description]*/function Watcher(vm, node, name, nodeType) {console.log('watcher');console.log(this);//Dep target是Dep和watcher关联的唯一桥梁Dep.target = this;/*** 关于Dep.target: 是一个全局变量,保存了当前的watcher* wathcer构造函数中,为Dep.target赋值后会通过watcher.update()方法去调用observe.get()* 在observer.get()方法中就是根据Dep.target来把当前watcher加入到订阅器Dep中* 所以在把wathcer加入完Dep后,Dep.target就没有用了,所以在this.update()后面要把Dep.watcher设为null*/this.name = name;this.node = node;this.vm = vm;this.nodeType = nodeType;this.update();Dep.target = null;}Watcher.prototype = {//更新视图上的值,{{ value }}update: function() {this.get();if (this.nodeType == 'text') {this.node.nodeValue = this.value;}if (this.nodeType == 'input') {this.node.value = this.value;}},// 获取 data 中的属性值get: function() {this.value = this.vm[this.name]; // 触发相应属性的 get}}function Vue(options) {console.log(this); //this是指向实例对象this.data = options.data;var data = this.data;observe(data, this); //第一步 监听vue的实例vm中data的所有属性var id = options.el;var dom = nodeToFragment(document.getElementById(id), this);//第二部 数据劫持做处理document.getElementById(id).appendChild(dom);//第三部 绑定到节点树}var vm = new Vue({el: 'app',data: {text: 'hello world'}})
