参考vue.js实现双向绑定的方法理解双向绑定原理(:Object.defineProperty和发布-订阅模式)

  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>
原文地址:https://www.cnblogs.com/CodeDeath/p/6323456.html