这篇文章我会仿照vue写一个双向绑定的实例,主要实v-model , v-bind , v-click
1、原理
Vue的双向数据绑定的原理大家可能或多或少了解一点,主要是通过 Object 对象的 defineProperty 属性,重写data的 set 和 get 函数来实现的。
2、页面结构
包含了
-
一个input,使用v-model指令
-
一个button,使用v-click指令
-
一个h3,使用v-bind指令。
我们最后会通过类似于vue的方式来使用我们的双向数据绑定,结合我们的数据结构添加注释:
首先我们需要定义一个myVue构造函数:
为了初始化这个构造函数,给它添加一个 _init
属性:
接下来实现 _obverse
函数,对data进行处理,重写data的set和get函数:
并改造_init函数
接下来我们写一个指令类Watcher,用来绑定更新函数,实现对DOM元素的更新。
更新 _init
函数以及 \_obverse
函数:
那么如何将view与model进行绑定呢?接下来我们定义一个 _compile
函数,用来解析我们的指令(v-bind,v-model,v-clickde)等,并在这个过程中对view与model进行绑定。
至此,我们已经实现了一个简单vue的双向绑定功能,包括v-bind, v-model, v-click三个指令。效果如下图:
附上全部代码
1 <!DOCTYPE html>
2
3 <head>
4
5 <title>myVue</title>
6
7 </head>
8
9 <style>
10
11 #app {
12
13 text-align: center;
14
15 }
16
17 </style>
18
19 <body>
20
21 <div id="app">
22
23 <form>
24
25 <input type="text" v-model="number">
26
27 <button type="button" v-click="increment">增加</button>
28
29 </form>
30
31 <h3 v-bind="number"></h3>
32
33 </div>
34
35 </body>
36
37 <script>
38
39 function myVue(options) {
40
41 this._init(options);
42
43 }
44
45 myVue.prototype._init = function (options) {
46
47 this.$options = options;
48
49 this.$el = document.querySelector(options.el);
50
51 this.$data = options.data;
52
53 this.$methods = options.methods;
54
55 this._binding = {};
56
57 this._obverse(this.$data);
58
59 this._complie(this.$el);
60
61 }
62
63 myVue.prototype._obverse = function (obj) {
64
65 var value;
66
67 for (key in obj) {
68
69 if (obj.hasOwnProperty(key)) {
70
71 this._binding[key] = {
72
73 _directives: []
74
75 };
76
77 value = obj[key];
78
79 if (typeof value === 'object') {
80
81 this._obverse(value);
82
83 }
84
85 var binding = this._binding[key];
86
87 Object.defineProperty(this.$data, key, {
88
89 enumerable: true,
90
91 configurable: true,
92
93 get: function () {
94
95 console.log(`获取${value}`);
96
97 return value;
98
99 },
100
101 set: function (newVal) {
102
103 console.log(`更新${newVal}`);
104
105 if (value !== newVal) {
106
107 value = newVal;
108
109 binding._directives.forEach(function (item) {
110
111 item.update();
112
113 })
114
115 }
116
117 }
118
119 })
120
121 }
122
123 }
124
125 }
126
127 myVue.prototype._complie = function (root) {
128
129 var _this = this;
130
131 var nodes = root.children;
132
133 for (var i = 0; i < nodes.length; i++) {
134
135 var node = nodes[i];
136
137 if (node.children.length) {
138
139 this._complie(node);
140
141 }
142
143 if (node.hasAttribute('v-click')) {
144
145 node.onclick = (function () {
146
147 var attrVal = nodes[i].getAttribute('v-click');
148
149 return _this.$methods[attrVal].bind(_this.$data);
150
151 })();
152
153 }
154
155 if (node.hasAttribute('v-model') && (node.tagName == 'INPUT' || node.tagName == 'TEXTAREA')) {
156
157 node.addEventListener('input', (function(key) {
158
159 var attrVal = node.getAttribute('v-model');
160
161 _this._binding[attrVal]._directives.push(new Watcher(
162
163 'input',
164
165 node,
166
167 _this,
168
169 attrVal,
170
171 'value'
172
173 ))
174
175 return function() {
176
177 _this.$data[attrVal] = nodes[key].value;
178
179 }
180
181 })(i));
182
183 }
184
185 if (node.hasAttribute('v-bind')) {
186
187 var attrVal = node.getAttribute('v-bind');
188
189 _this._binding[attrVal]._directives.push(new Watcher(
190
191 'text',
192
193 node,
194
195 _this,
196
197 attrVal,
198
199 'innerHTML'
200
201 ))
202
203 }
204
205 }
206
207 }
208
209 function Watcher(name, el, vm, exp, attr) {
210
211 this.name = name; //指令名称,例如文本节点,该值设为"text"
212
213 this.el = el; //指令对应的DOM元素
214
215 this.vm = vm; //指令所属myVue实例
216
217 this.exp = exp; //指令对应的值,本例如"number"
218
219 this.attr = attr; //绑定的属性值,本例为"innerHTML"
220
221 this.update();
222
223 }
来源:oschina
链接:https://my.oschina.net/u/4391488/blog/3912225