-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathcompile.js
273 lines (235 loc) · 7.03 KB
/
compile.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
class Compile {
constructor(el, vm) {
this.$vm = vm;
// 拿到它的属性节点
this.$el = this.isElementNode(el) ? el : document.querySelector(el);
if (this.$el) {
this.$fragment = this.node2Fragment(this.$el);
this.init();
this.$el.appendChild(this.$fragment);
}
}
/**
* https://developer.mozilla.org/zh-CN/docs/Web/API/Document/createDocumentFragment
* @param {元素节点} el
* DocumentFragments 是DOM节点。它们不是主DOM树的一部分。通常的用例是创建文档片段,将元素附加到文档片段,
* 然后将文档片段附加到DOM树。在DOM树中,文档片段被其所有的子元素所代替。因为文档片段存在于内存中,并不在DOM树中,
* 所以将子元素插入到文档片段时不会引起页面回流(reflow)(对元素位置和几何上的计算)。
* 因此,使用文档片段document fragments 通常会起到优化性能的作用(better performance)。
*/
node2Fragment (el) {
let fragment = document.createDocumentFragment(),
child;
// 将原生节点拷贝到fragment
while (child = el.firstChild) {
fragment.appendChild(child);
}
return fragment;
}
/**
* 初始化函数
*/
init () {
this.compileElement(this.$fragment);
}
/**
*
* @param {fragent对象(node节点)} el
*
*/
compileElement (el) {
let childNodes = el.childNodes;
// 把类数组对象转变成数组对象 1.Array.from 2.[].slice.call()
[...childNodes].forEach((node) => {
let text = node.textContent;
let reg = /\{\{(.*)\}\}/;
// 1.元素类型
if (this.isElementNode(node)) {
this.compile(node);
// 2. 文本类型
} else if (this.isTextNode(node) && reg.test(text)) {
this.compileText(node, RegExp.$1);
}
// 如果有子节点则递归遍历
if (node.childNodes && node.childNodes.length) {
this.compileElement(node);
}
})
}
/**
*
* @param {元素节点类型} node
*/
compile (node) {
// 拿到元素的属性类数组集合
// [
// {
// name: 'v-model',
// value: 'abc'
// },
// {
// name: 'v-on:click',
// value: 'onClick'
// },
// {
// name: 'v-bind:value',
// value: 'abc'
// }
// ]
let nodeAttrs = node.attributes;
[...nodeAttrs].forEach((attr) => {
let attrName = attr.name;
if (this.isDirective(attrName)) {
let exp = attr.value;
let dir = attrName.substring(2);
// 事件指令
if (this.isEventDirective(dir)) {
compileUtil.eventHandler(node, this.$vm, exp, dir);
} else {
// 普通指令
if (this.isBindDirective(dir)) {
compileUtil['bind'] && compileUtil['bind'](node, this.$vm, exp, dir.split(':')[1]);
} else {
compileUtil[dir] && compileUtil[dir](node, this.$vm, exp);
}
}
}
});
}
/**
*
* @param {文本节点类型} node
* @param {表达式} exp
* 编译文本节点
*/
compileText (node, exp) {
compileUtil.text(node, this.$vm, exp);
}
/**
*
* @param {指令} dir
* 是否是bind指令
*/
isBindDirective (dir) {
return dir.indexOf('bind:') === 0
}
/**
*
* @param {属性名称}} attr
* 是否是vue指令
*/
isDirective (attr) {
return attr.indexOf('v-') == 0;
}
/**
*
* @param {指令} dir
* 是否是事件指令
*/
isEventDirective (dir) {
return dir.indexOf('on') === 0;
}
/**
*
* @param {node节点} node
* 是否是元素节点
*/
isElementNode (node) {
return node.nodeType == 1;
}
/**
*
* @param {node节点} node
* 是否是文本节点
*/
isTextNode (node) {
return node.nodeType == 3;
}
}
// 指令处理集合
let compileUtil = {
value (node, vm, exp) {
this.bind(node, vm, exp, 'value');
},
text (node, vm, exp) {
this.bind(node, vm, exp, 'text');
},
html (node, vm, exp) {
this.bind(node, vm, exp, 'html');
},
model (node, vm, exp) {
this.bind(node, vm, exp, 'model');
let me = this,
val = this._getVMVal(vm, exp);
node.addEventListener('input', function (e) {
let newValue = e.target.value;
if (val === newValue) {
return;
}
me._setVMVal(vm, exp, newValue);
val = newValue;
});
},
class (node, vm, exp) {
this.bind(node, vm, exp, 'class');
},
bind (node, vm, exp, dir) {
//
let updaterFn = updater[dir + 'Updater'];
updaterFn && updaterFn(node, this._getVMVal(vm, exp));
new Watcher(vm, exp, function (value, oldValue) {
updaterFn && updaterFn(node, value, oldValue);
});
},
// 事件处理
eventHandler (node, vm, exp, dir) {
// onClick => click
let eventType = dir.split(':')[1],
// 拿到 options里的 方法
fn = vm.$options.methods && vm.$options.methods[exp];
if (eventType && fn) {
node.addEventListener(eventType, fn.bind(vm), false);
}
},
_getVMVal (vm, exp) {
let val = vm;
exp = exp.split('.');
exp.forEach(function (k) {
val = val[k];
});
return val;
},
_setVMVal (vm, exp, value) {
let val = vm;
exp = exp.split('.');
exp.forEach(function (k, i) {
// 非最后一个key,更新val的值
if (i < exp.length - 1) {
val = val[k];
} else {
val[k] = value;
}
});
}
};
// 指令对应的更新函数集合
let updater = {
valueUpdater (node, value) {
node.value = typeof value == 'undefined' ? '' : value
},
textUpdater (node, value) {
node.textContent = typeof value == 'undefined' ? '' : value;
},
htmlUpdater (node, value) {
node.innerHTML = typeof value == 'undefined' ? '' : value;
},
classUpdater (node, value, oldValue) {
let className = node.className;
className = className.replace(oldValue, '').replace(/\s$/, '');
let space = className && String(value) ? ' ' : '';
node.className = className + space + value;
},
modelUpdater (node, value, oldValue) {
node.value = typeof value == 'undefined' ? '' : value;
}
};