2

写在前面

模板的诞生是为了将显示与数据分离,模板技术多种多样,但其本质是将模板文件和数据通过模板引擎生成最终的HTML代码。目前有着很多这种模板引擎,诸如Node的ejsjade,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(/&lt;/g, '<').replace(/&gt;/g, '>'); // 将&lt;和&gt;替换
        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,当然这个模板引擎只是一个玩物,并不能用于生产,比如,根本没有考虑到渲染过程中出错时如何定位到具体位置等问题。


zp1996
3.2k 声望65 粉丝

coder