Vue 动态数据绑定(四)-- 解析器 Compile

任务说明地址

将页面中代码片段

1
2
3
4
5
<!-- 页面中原本的 html 模板片段 -->
<div id="app">
<p>姓名:{{user.name}}</p>
<p>年龄:{{user.age}}</p>
</div>

渲染成实际效果

1
2
3
4
5
<!-- 最终在页面中渲染出来的结果 -->
<div id="app">
<p>姓名:youngwind</p>
<p>年龄:25</p>
</div>

所以问题的关键:怎么实现一个解析器进行模板渲染?

简单的思路:深度遍历DOM模板,找到{{}}进行解析替换。
按照此思路实现一个简单的版本:
代码 Demo

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

function Compile(el, vm) {
this.el = el;
this.vm = vm;
this.render();
}
Compile.prototype.render = function () {
var el = document.querySelector(this.el);
this.compile(el);
}
Compile.prototype.compile = function (el) {
// 递归遍历所有的dom子元素 找到 {{}} 进行替换

var childNodes = el.childNodes;
childNodes.forEach((item) => {

if(item.nodeType === 1) { // 元素节点
this.compile(item);
} else if (item.nodeType === 3) { // 文本内容
console.log(item.nodeValue);
var value = item.nodeValue.trim();
var reg = /{{(.*?)}}/;
if (value && reg.test(value)) {
this.compileText(item, reg.exec(value)[1]);
}
}
})
}
Compile.prototype.compileText = function(node, exp) {
var newVal = this.parse(exp.trim())(this.vm.data);
node.textContent = typeof newVal == 'undefined' ? '' : newVal;
}
Compile.prototype.parse = function (exp) { // 多层路径进行解析
if (/[^\w.$]/.test(exp)) return;

var exps = exp.split('.');
return function(obj) {
for (var i = 0, len = exps.length; i < len; i++) {
if (!obj) return;
obj = obj[exps[i]];
}
return obj;
}
}

上述实现方法是找到DOM直接替换,在遍历解析的时候有多次操作DOM节点,为了提高性能和效率,考虑用文档碎片fragment的方式。初始化时将节点转换成 fragment,解析完成再整体添加到真实的DOM节点中。

代码 DEMO

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
function Compile(el, vm) {
this.el = el;
this.vm = vm;
this.fragement = this.nodeToFragment(document.querySelector(this.el));
this.compile(this.fragement);
document.querySelector(this.el).appendChild(this.fragement);
}
Compile.prototype.nodeToFragment = function(el) {
var fragment = document.createDocumentFragment();
var child = el.firstChild;
while (child) {
fragment.appendChild(child); // 将Dom元素移入fragment中 注意: append的时候 原来的dom会删掉挂载在文档碎片上
child = el.firstChild;
}
return fragment;
}