<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<div id="app"></div>
<script>
class Mustache {
static parseTemplateToTokens(str){
let tokens = [];
let scanner = new Scanner(str);
let words;
while(!scanner.isEnd()){
words = scanner.scan('{{');
if (words != ''){
let inLabel = false;
let _words = '';
for(let i = 0; i < words.length; i++){
if(words[i] === '<') {
inLabel = true;
}else if (words[i] === '>') {
inLabel = false;
}
if(!/\s/.test(words[i])){
_words += words[i];
} else if (inLabel) {
_words += ' ';
}
}
tokens.push(['text', _words])
}
scanner.skip('{{');
words = scanner.scan('}}');
if(words !== ''){
if(words[0] === '#'){
tokens.push(['#', words.substring(1)]);
} else if (words[0] === '/'){
tokens.push(['/', words.substring(1)]);
} else {
tokens.push(['name', words]);
}
}
scanner.skip('}}');
}
return Mustache.nestTokens(tokens);
}
static nestTokens(tokens){
let nestedTokens = [];
let sections = [];
let collector = nestedTokens;
for(let i = 0; i < tokens.length; i++){
let token = tokens[i];
switch(token[0]){
case '#':
collector.push(token);
sections.push(token);
collector = token[2] = [];
break;
case '/':
sections.pop();
collector = sections.length > 0 ? sections[sections.length - 1][2] : nestedTokens;
break;
default:
collector.push(token);
}
}
return nestedTokens;
}
static renderTemplate(tokens, data) {
let result = '';
for(let i = 0; i < tokens.length; i++){
let token = tokens[i];
if(token[0] === 'text'){
result += token[1];
} else if (token[0] === 'name') {
result += Mustache.lookup(data, token[1]);
} else if (token[0] === '#') {
result += Mustache.parseArray(token, data);
}
}
return result;
}
* @description: 使用连点号在对象中查找深层次的属性如 lookup(obj, 'a.b.c') => obj.a.b.c
* @param {Object} obj
* @param {String} keyName
* @return {any}
*/
static lookup(obj, keyName) {
if(keyName.indexOf('.') != -1 && keyName != '.') {
let keys = keyName.split('.');
let temp = obj;
for(let i = 0; i < keys.length; i++){
temp = temp[keys[i]];
}
return temp;
}
return obj[keyName];
}
static parseArray(token, data) {
let v = Mustache.lookup(data, token[1]);
let result = '';
for(let i = 0; i < v.length; i++){
result += Mustache.renderTemplate(token[2], {
...v[i],
'.': v[i]
});
}
return result;
}
static test() {
let templateStr = `
<div>
<ul>
{{#students}}
<li class="myli">
学生{{name}}的爱好是
<ol>
{{#hobbies}}
<li>{{.}}</li>
{{/hobbies}}
</ol>
</li>
{{/students}}
</ul>
</div>
`;
let data = {
students: [
{ 'name': '小明', 'hobbies': ['编程', '游泳'] },
{ 'name': '小红', 'hobbies': ['看书', '弹琴', '画画'] },
{ 'name': '小强', 'hobbies': ['锻炼'] }
]
};
let tokens = Mustache.parseTemplateToTokens(templateStr);
let domStr = Mustache.renderTemplate(tokens, data);
let dom = document.querySelector('#app');
dom.innerHTML = domStr;
}
}
class Scanner {
constructor(str){
this.templateStr = str;
this.pos = 0;
this.tail = str;
}
scan(tag){
const start = this.pos;
while(!this.isEnd() && this.tail.indexOf(tag) != 0){
this.pos++;
this.tail = this.templateStr.substring(this.pos);
}
return this.templateStr.substring(start, this.pos);
}
skip(tag){
if(this.tail.indexOf(tag) === 0){
this.pos+= tag.length;
this.tail = this.templateStr.substring(this.pos);
}
}
isEnd() {
return this.pos >= this.templateStr.length;
}
}
Mustache.test();
</script>
</body>
</html>