前言:
相信很多的老铁多多少少知道Javascript的模板引擎但是实际用过的伙伴可能没多少。在文章开始之前我先简单的介绍一下:JavaScript的模板引擎是一种用于生成动态HTML内容的工具, 其实说白了就是在运行时,模板引擎将动态地替换模板中的占位符,生成最终的HTML输出。
那么废话不多说,我们直接开整!
要实现的效果
开始之前我们得知道最终实现的效果是什么样的,如下图
js
let TemplateEngine = function(tpl, data) {
// magic here ...
}
let template = 'Hello, my name is <%name%>. I am <%age%> years old this year;'
console.log(TemplateEngine(template, {
name: "Tom",
age: 19
}));
// 输入:Hello, my name is Tom. I am 19 years old this year
获取内容
js
var re = /<%([^%>]+)?%>/g;
let match = re.exec(tpl);
// 输入:
// [
// "<%name%>",
// " name ",
// index: 21,
// input:
// "Hello, my name is <%name%>. I am <%age%> years old this year;"
// ]
看不懂上面的正则没关系直接上图,Tips: 这个小玩意可以保存看一下研究正则的时候用的到哦

我们需要吧<% 内容 %> 获取到,同时采用exec的方法; 然后使用while循环进行全部匹配的遍历
exec 和**match**方法的区别就是:
match()
方法适合用于直接得到整个匹配结果数组或获取第一个匹配结果,而不需要连续搜索。exec()
方法适合用于需要连续搜索并获取多个匹配结果的情况,并且可以通过更新lastIndex
属性来实现连续搜索
js
let TemplateEngine = function (tpl, data) {
let re = /<%([^%>]+)?%>/g,
match;
while ((match = re.exec(tpl))) {
console.log(match);
}
};
输出如下:

得到上面的结果,我们想到最简单的方法是什么?替换 replace
js
let TemplateEngine = function (tpl, data) {
let re = /<%([^%>]+)?%>/g,
match;
while ((match = re.exec(tpl))) {
tpl = tpl.replace(match[0], data[match[1]]);
}
return tpl;
};
let result = TemplateEngine(template, {
name: "Tom",
age: 19
})
console.log(result);
// 输入:
// Hello, my name is Tom. I am 19 years old this year;
但是问题也会随时产生、如果数据变成这样呢?
js
console.log(TemplateEngine(template, {
name: "Tom",
age: {
muns:19
}
}));
好像不行,我们需要更好的方式!
Function 构造函数
还记得 Function 吗?是的!就是构造函数!它可以使用字符串的方式运行我们的JavaScript代码!比如这样:
js
const sum = new Function('a', 'b', 'return a + b');
console.log(sum(2, 6));
// Expected output: 8
// or 跟下方的代码同理
const sum = function (a, b) {
return a + b;
};
console.log(sum(2, 6));
我们可以把循环的语句变成这样同理对象也是,然后用数组进行收集、然后通过join变成字符串
js
let r=[];
r.push("Hello, my name is ");
r.push( name );
r.push(". I am ");
r.push( profile.age );
r.push(" years old this year;");
return r.join("");
初始版
那么我们初步的版本应该实现成这样:
js
const TemplateEngine = function (tpl, data) {
let re = /<%(.+?)%>/g;
let code = "let r=[];\n";
let cursor = 0;
let match = null;
const add = function (line, js) {
if (js) {
code += "r.push(" + line + ");\n";
} else {
code += 'r.push("' + line.replace(/"/g, '\"') + '");\n';
}
};
while ((match = re.exec(tpl))) {
let trs = tpl.slice(cursor, match.index);
add(trs);
add(match[1], true); // 重要的
cursor = match.index + match[0].length;
}
add(tpl.substr(cursor, tpl.length - cursor));
code += `return r.join("");`;
console.log(code);
return tpl;
};
let template = "Hello, my name is <% name %>. I am <% profile.age %> years old this year;";
const res = TemplateEngine(template, {
name: "Krasimir Tsonev",
profile: { age: 29 },
});
console.log(res);
// 打印如下:
// let r=[];
// r.push("Hello, my name is ");
// r.push( name );
// r.push(". I am ");
// r.push( profile.age );
// r.push(" years old this year;");
// return r.join("");
得到了上面的字符串为基础,我们要做的就是执行它!
修复版
关键字:with
js
const TemplateEngine = function (tpl, data) {
let re = /<%(.+?)%>/g;
let code = "with(obj) { let r=[];\n";
let cursor = 0;
let match = null;
let result = null;
const add = function (line, js) {
if (js) {
code += "r.push(" + line + ");\n";
} else {
code += 'r.push("' + line.replace(/"/g, '\"') + '");\n';
}
};
while ((match = re.exec(tpl))) {
let m = tpl.slice(cursor, match.index);
add(m);
add(match[1], true);
cursor = match.index + match[0].length;
}
add(tpl.substr(cursor, tpl.length - cursor));
code += `return r.join(""); }`;
code = code.replace(/[\r\t\n]/g, " ");
// 最终需要执行的
result = new Function("obj", code).apply(data, [data]);
return result;
};
let template =
"Hello, my name is <% name %>. I am <% profile.age %> years old this year;";
const res = TemplateEngine(template, {
name: "Krasimir Tsonev",
profile: { age: 29 },
});
console.log(res);
到现在我们已经可以对一些对象数据进行赋值,让我们在添加一些边界的情况支持if/else 和循环
js
const TemplateEngine = function (tpl, data) {
let re = /<%(.+?)%>/g;
let code = "with(obj) { let r=[];\n";
let reExp = /(^( )?(if|for|else|switch|case|break|{|}))(.*)?/g;
let cursor = 0;
let match = null;
let result = null;
const add = function (line, js) {
if (js) {
code += line.match(reExp) ? line + '\n' : 'r.push(' + line + ');\n'
} else {
code += 'r.push("' + line.replace(/"/g, '\"') + '");\n';
}
};
while ((match = re.exec(tpl))) {
let m = tpl.slice(cursor, match.index);
add(m);
add(match[1], true);
cursor = match.index + match[0].length;
}
add(tpl.substr(cursor, tpl.length - cursor));
code += `return r.join(""); }`;
code = code.replace(/[\r\t\n]/g, " ");
result = new Function("obj", code).apply(data, [data]);
return result;
};
let template =
`<div>
<h1><% title %></h1>
<ul>
<% for(var i = 0; i < items.length; i++) { %><li><% items[i] %></li><% } %>
</ul>
</div>`;
let res = TemplateEngine(template, {
title:"测试",
items: ["项目1", "项目2", "项目3"],
});
document.body.innerHTML = res;
效果如下:

上面的代码似乎有点子繁琐啊、让我们在修订一个版本试试~
最终版本:
js
const TemplateEngine = function(html, options) {
var re = /<%(.+?)%>/g,
reExp = /(^( )?(var|if|for|else|switch|case|break|{|}|;))(.*)?/g,
code = 'with(obj) { var r=[];\n',
cursor = 0,
result,
match;
var add = function(line, js) {
js? (code += line.match(reExp) ? line + '\n' : 'r.push(' + line + ');\n') :
(code += line != '' ? 'r.push("' + line.replace(/"/g, '\"') + '");\n' : '');
return add;
}
while(match = re.exec(html)) {
add(html.slice(cursor, match.index))(match[1], true);
cursor = match.index + match[0].length;
}
add(html.substr(cursor, html.length - cursor));
code = (code + 'return r.join(""); }').replace(/[\r\t\n]/g, ' ');
try { result = new Function('obj', code).apply(options, [options]); }
catch(err) { console.error("'" + err.message + "'", " in \n\nCode:\n", code, "\n"); }
return result;
}
最后
上面的代码也是我在逛github上偶然看到觉得不错,特此记录一下!如果感兴趣大家可以关注这个项目看看~,
当然如果能帮助到大家的那么十分荣幸~ 喜欢的老铁们不用拘束了,可以点赞了~