写在前面
模板的诞生是为了将显示与数据分离,模板技术多种多样,但其本质是将模板文件和数据通过模板引擎生成最终的HTML代码。目前有着很多这种模板引擎,诸如Node的ejs
,jade
,PHP的Smarty
。当然在用过这么多的模板引擎后,也有着自己实现一个简易模板引擎的冲动。于是今天就实现了一个简单的模板引擎,这个模板引擎非常简单,并不会涉及到语法分析,词法分析等编译原理相关知识,做的仅仅是将模板的js
代码执行。下面来和大家分享下:
实现模板引擎
语法参照的是ejs
:
<% js code %>
<%= 变量名 %>
看下需要实现的目标:
// 模板代码
<ul>
<% for (var i = 0; i < 5; i++) { %>
<% if (i === 0) { %>
<li><%= i %></li>
<% } else { %>
<li><%= (i + 11) %></li>
<% } %>
<% } %>
/ul>
// 转化为html后
<ul>
<li>0</li>
<li>12</li>
<li>13</li>
<li>14</li>
<li>15</li>
</ul>
下面来考虑下,其实形成上述的html
的话,利用js
拼接字符串就可以做到,那么对于模板来说,是不是可以将模板内的js
代码转化为原生js
代码从而来实现呢?没错,这个简单的模板引擎干的就是这个事情,就是将模板内的被<% %>
包裹的js
代码执行,从而将模板转为html
。
但是,如何做呢?可以采用Function(参数名, 函数主体)
,虽说这个平时不怎么使用,但是在这个情况下是最好的选择,将模板内的js
代码解析提取后当做参数传入到Function
中:
/**
* 将模板引擎转化为可用dom字符串
*
* @param {String} tpl
* @param {Object} data 数据对象,为键值对形式,键值为数据名
* @return {String}
*/
function tpl2dom (tpl, data) {
var nbspRE = /\s{2,}/g,
quotRE = /\"/g,
main = ""; // 函数主体
function fn (d) {
var i, keys = [], vals = [];
for (i in d) {
keys.push(i); // 参数名
vals.push(d[i]); // 参数对应的值
}
return (new Function(keys, main)).apply(d, vals);
}
if (!main) {
tpl = tpl.replace(/</g, '<').replace(/>/g, '>'); // 将<和>替换
var tpls = tpl.split("<%"),
len = tpls.length;
main = `var res = "${tpls[0].replace(nbspRE, '').replace(quotRE, "\'")}";\n`;
// res就是拼接的html字符串
for (var i = 1; i < len; i++) {
var p = tpls[i].split("%>");
// 发现第一个字符为`=`号时,直接进行赋值操作
main += 0x3D === p[0].charCodeAt(0) ?
`\nres += ${p[0].substr(1)}`:
`\n${p[0].replace(/\r\n/g, "").trim()}`;
main += `\nres += "${p[p.length - 1]
.replace(nbspRE, '')
.replace(quotRE, "\'")
.replace(/[\r\n]/g, '')}"`;
}
main += "\nreturn res;";
}
return data ? fn(data) : fn();
}
利用tpl2dom
函数可以得到html
,那下面在写一个函数来将html
转为dom
节点。从下面的函数可以看出的就是,这个模板引擎所解析的模板,只能有一个dom
根节点:
var cacheDiv = document.createElement("div");
/**
* 将可用dom字符串转为dom节点
*
* @param {String} str
* @return {DOM}
*/
function str2dom (str) {
cacheDiv.innerHTML = "";
cacheDiv.innerHTML = str;
// 由此可看出,模板只能有一个根节点
return cacheDiv.childNodes[0];
}
到此为止,模板引擎就基本完成了,既然是仿照的ejs
,那么在来完善下,比如img_tag
:
// 注意,实现该函数需放到全局
function img_tag (url) {
return `<img src="${url}" />`
}
完整代码请见github,当然这个模板引擎只是一个玩物,并不能用于生产,比如,根本没有考虑到渲染过程中出错时如何定位到具体位置等问题。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。