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 >
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>