1 <!DOCTYPE html> 2 <html lang="en"> 3 <head> 4 <meta charset="UTF-8"> 5 <title>Object.defineProperty</title> 6 <style> 7 .decimal-leading-zero{list-style-type: decimal-leading-zero} 8 </style> 9 </head> 10 <body> 11 <p>参考vue.js实现双向绑定的方法理解双向绑定原理(:Object.defineProperty和发布-订阅模式)</p> 12 <h3>前端MVVM原理--参考vue.js实现</h3> 13 <ul class="decimal-leading-zero"> 14 <li>Objdect.defineProperty实现属性劫持</li> 15 <li>实现一个Observer,能够对数据的所有的属性进行监听,如有变动可拿到最新值并通知订阅者</li> 16 <li>实现一个Compile,对每个元素节点的指令进行扫描和解析,根据指令模板替换相应数据并绑定相应更新函数</li> 17 <li>实现Watcher,作为连接Observer和Compile的桥梁,能够订阅并收到每一个属性变动的通知,并执行指令绑定的相应更新函数,更新视图</li> 18 <li>mvvm入口函数,整合以上三者</li> 19 </ul> 20 21 <div id="app"> 22 <input type="text" v-model="textvalue"> 23 {{ textvalue }} 24 <input type="text" v-model="text"> 25 {{ text }} {{ text }} 26 </div> 27 <script> 28 var uid$1 = 0; 29 function Watcher(vm, node, name){ 30 Dep.target = this; 31 this.name = name; 32 this.node = node; 33 this.vm = vm; 34 this.uid = uid$1++; 35 this.update(); 36 Dep.target = null; 37 }; 38 39 Watcher.prototype = { 40 update: function(){ 41 this.get(); 42 this.node.nodeValue = this.value; 43 }, 44 get:function(){ //获取data中的属性值 45 this.value = this.vm[this.name]; 46 } 47 }; 48 49 function compile(node, vm){ 50 var reg = new RegExp(/{{(.*?)}}/g); //正则匹配指令({{ text }}) 51 if(node.nodeType === 1){ //匹配节点元素 52 var attr = node.attributes; //获取节点元素的所有属性 53 //解析属性 54 for (var i = 0; i < attr.length; i++) { 55 if(attr[i].nodeName == 'v-model'){ 56 var name = attr[i].nodeValue; //获取v-model绑定的属性名 57 node.addEventListener('input', function(e){ 58 //给相应的data属性赋值,并触发该属性的set方法 59 vm[name] = e.target.value; 60 }); 61 node.value = vm.data[name]; //将data值赋值给node 62 node.removeAttribute('v-model'); 63 }; 64 }; 65 }; 66 if (node.nodeType === 3) { //匹配节点类型为text的元素 67 if (reg.test(node.nodeValue.trim())) { //去除空格,防止指令前后有空格的状况 68 var nodeValue = node.nodeValue.trim(); 69 nodeValue.match(reg).forEach(function(key){ 70 var name = key.replace(/{{(.*?)}}/g,RegExp.$1); //获取匹配到的字符串 71 name = name.trim(); 72 //node.nodeValue = vm.data[name]; //将data值赋值给node 73 new Watcher(vm, node, name); //这里改成订阅者形式,从而实现自动更新绑定相同指令的元素 74 }); 75 }; 76 }; 77 }; 78 79 function nodeToFragment(node, vm){ 80 var flag = document.createDocumentFragment(); 81 var child; 82 83 //循环遍历节点,编译节点并劫持到文档片段中 84 while(child = node.firstChild){ 85 compile(child, vm); //根据指令模板编译节点指令 86 flag.append(child); //将子节点劫持到文档片段中 87 }; 88 89 return flag; //返回文档片段 90 }; 91 92 function Dep(){ 93 this.subs = []; 94 }; 95 96 Dep.prototype = { 97 addSub: function(sub){ 98 if(!this.subs[sub.uid]){ 99 //防止重复添加 100 this.subs[sub.uid] = sub; 101 } 102 }, 103 notify: function(){ 104 for(var uid in this.subs){ 105 this.subs[uid].update(); 106 } 107 } 108 } 109 110 function defineReactive(obj, key, val){ 111 var dep = new Dep(); 112 113 Object.defineProperty(obj, key, { 114 get: function(){ 115 //添加订阅者watcher到主体对象Dep中 116 if(Dep.target) dep.addSub(Dep.target); 117 return val; 118 }, 119 set: function(newVal){ 120 if (newVal === val) return; 121 val = newVal; 122 //console.log(val); 123 //作为发布者发出通知 124 dep.notify(); 125 } 126 }); 127 }; 128 129 function observe(obj, vm){ 130 Object.keys(obj).forEach(function(key){ 131 defineReactive(vm, key, obj[key]); 132 }); 133 }; 134 135 function vue(options){ 136 this.data = options.data; 137 var data = this.data; 138 139 observe(data, this); 140 141 var id = options.el; 142 var dom = nodeToFragment(document.getElementById(id), this); 143 //编译完成后,将dom重新赋值给app 144 document.getElementById(id).appendChild(dom); 145 }; 146 </script> 147 <script> 148 var vm = new vue({ 149 el: 'app', 150 data: { 151 textvalue: 'hello world', 152 text: 'hello' 153 } 154 }); 155 </script> 156 157 <script> 158 //视图控制器 159 // var userInfo = {}; 160 // Object.defineProperty(userInfo, "nickName", { 161 // get: function(){ 162 // return document.getElementById('nickName').innerHTML; 163 // }, 164 // set: function(nick){ 165 // document.getElementById('nickName').innerHTML = nick; 166 // } 167 // }); 168 // Object.defineProperty(userInfo, "introduce", { 169 // get: function(){ 170 // return document.getElementById('introduce').innerHTML; 171 // }, 172 // set: function(introduce){ 173 // document.getElementById('introduce').innerHTML = introduce; 174 // } 175 // }) 176 </script> 177 178 <script> 179 // //定义一个发布者 180 // var publisher = { 181 // publish: function(){ 182 // dep.notify(); 183 // } 184 // }; 185 186 // //定义三个订阅者 187 // var subscriber1 = {update: function(){console.log(1);}}; 188 // var subscriber2 = {update: function(){console.log(2);}}; 189 // var subscriber3 = {update: function(){console.log(3);}}; 190 191 // //定义一个主体对象,用于存放订阅者 192 // function Dep(){ 193 // this.subscribers = [subscriber1,subscriber2,subscriber3]; 194 // }; 195 196 // //定义主体对象的原型方法notify,用于调用订阅者的更新方法,从而实现订阅更新操作 197 // Dep.prototype.notify = function() { 198 // this.subscribers.forEach(function(subscriber){ 199 // subscriber.update(); 200 // }); 201 // }; 202 203 // //发布者发布消息,主体对象执行notify方法,进而触发订阅者执行update方法 204 // var dep = new Dep(); 205 // publisher.publish(); 206 </script> 207 </body> 208 </html>