本文已参与「新人创作礼」活动,一起开启掘金创作之路。
模板引擎
阅读vue和mustache源码后,自己实现一个简易版的模板引擎
实现思路
1、获取页面模板字符串
let template = document.getElementById(id).innerHTML
2、解析成树结构tokens数组,通过栈管理
3、 将数据替换上去然后渲染到页面上
// 渲染
function resuleStr(Tokens, data) {
let resuleStr = ''
Tokens.forEach((item, index) => {
if (item[0] == 'text') {
resuleStr += item[1]
} else if (item[0] == 'name') {
resuleStr += lookup(data, item[1])
} else if (item[0] == '#') {
resuleStr += arrRender(item[2], data[item[1]])
}
})
return resuleStr
}
// 循环数组处理
function arrRender(template, arrData) {
let str = ''
arrData.forEach((item) => {
str += resuleStr(template, item)
})
return str
}
代码
<!DO
CTYPE html>
<html>
<head>
<meta charset="utf-8">
<title></title>
</head>
<script src="./templateEngine.js"></script>
<body>
<div id="container">
<div>
<ul>
{{#students}}
<li class="myli">
学生{{name}}的爱好是
<ol>
{{#hobbies}}
<li>{{.}}</li>
{{/hobbies}}
</ol>
<h1>{{a.b.c}}</h1>
</li>
{{/students}}
</ul>
</div>
</div>
<script>
// 数据
let data = {
students: [{
'name': '小明',
'hobbies': ['编程', '游泳'],
a: {
b: {
c: '多层级测试1'
}
}
},
{
'name': '小红',
'hobbies': ['看书', '弹琴', '画画'],
a: {
b: {
c: '多层级测试2'
}
}
},
{
'name': '小强',
'hobbies': ['锻炼'],
a: {
b: {
c: '多层级测试3'
}
}
}
]
};
render('container', data)
</script>
</body>
</html>
templateEngine文件
class scanner {
constructor(template) {
this.index = 0 //下标
this.tail = template // 尾部
this.template = template
}
sacnText(tag) {
var startIndex = this.index; // 记录开始下标
while (this.tail.indexOf(tag) !== 0 && this.tail) {
this.index++
this.tail = this.template.substr(this.index)
}
var endText = this.template.substring(startIndex, this.index)
return endText
}
sacnTag(tag) {
this.index += tag.length
this.tail = this.tail.substring(tag.length)
return this.tail.substr(0, tag.length)
}
}
// 获取tokens
function getTokens(template) {
var token = new scanner(template)
var tokenArr = []
var word
while (token.index < template.length) {
word = token.sacnText("{{");
if (word) {
switch (word[0]) {
case '#':
tokenArr.push(["#", word.substr(1)])
break;
case '/':
tokenArr.push(["/", word.substr(1)])
break;
default:
// 智能判断是普通文字的空格,还是标签中的空格
// 标签中的空格不能去掉,比如<div class="box">不能去掉class前面的空格
let isInJJH = false;
// 空白字符串
var _words = '';
for (let i = 0; i < word.length; i++) {
// 判断是否在标签里
if (word[i] == '<') {
isInJJH = true;
} else if (word[i] == '>') {
isInJJH = false;
}
// 如果这项不是空格,拼接上
if (!/\s/.test(word[i])) {
_words += word[i];
} else {
// 如果这项是空格,只有当它在标签内的时候,才拼接上
if (isInJJH) {
_words += ' ';
}
}
}
tokenArr.push(["text", _words])
break;
}
}
token.sacnTag("{{")
word = token.sacnText("}}");
if (word) {
switch (word[0]) {
case '#':
tokenArr.push(["#", word.substr(1)])
break;
case '/':
tokenArr.push(["/", word.substr(1)])
break;
default:
tokenArr.push(["name", word])
break;
}
}
token.sacnTag("}}")
}
return handleTokens(tokenArr)
}
// 处理循环tokens
function handleTokens(tokens) {
var resultArr = [];
var loopArr = []
var temArr = resultArr;
tokens.forEach((item, index) => {
switch (item[0]) {
case '#':
temArr.push(item)
loopArr.push(item)
temArr = item[2] = []
break;
case "/":
loopArr.pop();
if (loopArr.length > 0) {
temArr = loopArr[loopArr.length - 1][2]
} else {
temArr = resultArr
}
break;
default:
temArr.push(item)
break
}
})
return resultArr
}
// 渲染
function resuleStr(Tokens, data) {
let resuleStr = ''
Tokens.forEach((item, index) => {
if (item[0] == 'text') {
resuleStr += item[1]
} else if (item[0] == 'name') {
resuleStr += lookup(data, item[1])
} else if (item[0] == '#') {
resuleStr += arrRender(item[2], data[item[1]])
}
})
return resuleStr
}
// 循环数组处理
function arrRender(template, arrData) {
let str = ''
arrData.forEach((item) => {
str += resuleStr(template, item)
})
return str
}
// 解析a.b.c这种
function lookup(data, key) {
if (key.indexOf('.') !== -1 && key !== '.') {
return eval('data.' + key)
} else if (key === '.') {
return data
} else {
return data[key]
}
}
function render(id,data){
debugger
let template = document.getElementById(id).innerHTML
let Tokens = getTokens(template)
let innerHTMLStr = resuleStr(Tokens, data)
document.getElementById(id).innerHTML = innerHTMLStr
}