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>