1.vue数据双向绑定是通过数据劫持结合发布者-订阅者模式的方式来实现的;
数据劫持:通过Object.defineProperty( )来进行数据劫持的
注:Object.defineProperty( ):它可以来控制一个对象属性的一些特有操作,比如读写权、是否可以枚举
思路分析: MVVM 主要包含: 1.数据变化更新视图 (就是通过Object.defineProperty( )对属性设置一个set函数,当数据改变了就会来触发这个函数,所以我们只要将一些需要更 新的方法放在这里面就可以实现data更新view了。) 2. 视图变化更新数据(通过事件监听)
实现过程
我们已经知道实现数据的双向绑定,首先要对数据进行劫持监听,所以我们需要设置一个监听器Observer,用来监听所有属性。如果属性发上变化了,就需要告诉订阅者Watcher看是否需要更新。因为订阅者是有很多个,所以我们需要有一个消息订阅器Dep来专门收集这些订阅者,然后在监听器Observer和订阅者Watcher之间进行统一管理的。接着,我们还需要有一个指令解析器Compile,对每个节点元素进行扫描和解析,将相关指令对应初始化成一个订阅者Watcher,并替换模板数据或者绑定相应的函数,此时当订阅者Watcher接收到相应属性的变化,就会执行对应的更新函数,从而更新视图。因此接下去我们执行以下3个步骤,实现数据的双向绑定:
1.实现一个监听器Observer,用来劫持并监听所有属性,如果有变动的,就通知订阅者。
2.实现一个订阅者Watcher,可以收到属性的变化通知并执行相应的函数,从而更新视图。
3.实现一个解析器Compile,可以扫描和解析每个节点的相关指令,并根据初始化模板数据以及初始化相应的订阅器。
//实现一个监听器Observer,用来劫持并监听所有属性,如果有变动的,就通知订阅者。 function defineReactive(data,key,val) { observer(val) var dep = new Dep(); Object.defineProperty(data,key,{ enumerable:true, configurable:true, get:function () { if (Dep.target) { // 判断是否需要添加订阅者 dep.addSub(Dep.target); // 在这里添加一个订阅者 } return val }, set:function (newVla) { if(val===newVla){ return } val=newVla console.log("属性"+key+"已经被监听到了.现在值为:" + newVla.toString()) dep.notify(); // 如果数据变化,通知所有订阅者 } }) } Dep.target = null; function observer(data) { if(!data || typeof data!=="object"){ return } Object.keys(data).forEach(function (key) { defineReactive(data,key,data[key]) }) } function Dep() { this.subs=[] } Dep.prototype={ addSub:function (sub) { this.subs.push(sub) }, notify:function () { this.subs.forEach(function (sub) { sub.update(); }) } };
//2.实现一个订阅者Watcher,可以收到属性的变化通知并执行相应的函数,从而更新视图。 function Watcher(vm,exp,cb) { this.cb=cb this.vm=vm this.exp=exp this.value=this.get() //将自已添加到订阅器的操作 } Watcher.prototype={ update:function () { this.run() }, run:function () { var value=this.vm.data[this.exp]; var oldVal=this.value; if(value!==oldVal){ this.value=value this.cb.call(this.vm,value,oldVal) } }, get:function () { Dep.target=this; //缓存自已 var value=this.vm.data[this.exp] // 强制执行监听器里的get函数 Dep.target=null // 释放自己 return value } } function SelfVue (options) { var self = this; this.vm = this; this.data = options; Object.keys(this.data).forEach(function(key) { self.proxyKeys(key); }); observer(this.data); new Compile(options, this.vm); return this; } SelfVue.prototype = { proxyKeys: function (key) { var self = this; Object.defineProperty(this, key, { enumerable: false, configurable: true, get: function proxyGetter() { return self.data[key]; }, set: function proxySetter(newVal) { self.data[key] = newVal; } }); } }
//3.实现一个解析器Compile,可以扫描和解析每个节点的相关指令,并根据初始化模板数据以及初始化相应的订阅器 function nodeToFragment (el) { var fragment = document.createDocumentFragment(); var child = el.firstChild; while (child) { // 将Dom元素移入fragment中 fragment.appendChild(child); child = el.firstChild } return fragment; } function compileElement (el) { var childNodes = el.childNodes; var self = this; [].slice.call(childNodes).forEach(function(node) { var reg = /\{\{(.*)\}\}/; var text = node.textContent; if (self.isTextNode(node) && reg.test(text)) { // 判断是否是符合这种形式{{}}的指令 self.compileText(node, reg.exec(text)[1]); } if (node.childNodes && node.childNodes.length) { self.compileElement(node); // 继续递归遍历子节点 } }); } function compileText (node, exp) { var self = this; var initText = this.vm[exp]; this.updateText(node, initText); // 将初始化的数据初始化到视图中 new Watcher(this.vm, exp, function (value) { // 生成订阅器并绑定更新函数 self.updateText(node, value); }); } function updateText(node, value) { node.textContent = typeof value == 'undefined' ? '' : value; } function compile(node) { var nodeAttrs=node.attributes; var self=this Array.prototype.forEach.call(nodeAttrs,function (attr) { var attrName=attr.name if(self.isDirective(attrName)){ var exp=attr.value var dir=attrName.substring(2) if (self.isEventDirective(dir)) { // 事件指令 self.compileEvent(node, self.vm, exp, dir); } else { // v-model 指令 self.compileModel(node, self.vm, exp, dir); } node.removeAttribute(attrName); } }) }