美文网首页
自定义模版引擎

自定义模版引擎

作者: 我是上帝可爱多 | 来源:发表于2017-08-02 21:47 被阅读45次

    实现一个简单的模版引擎只需简单的正则匹配即可。

    var TemplateEngine = function(tpl, data) {
        // magic here ...
    }
    var template = '<p>Hello, my name is <%name%>. I\'m <%age%> years old.</p>';
    console.log(TemplateEngine(template, {
        name: "Krasimir",
        age: 29
    }));
    通过上面一个函数,我想把< %去掉,并把变量赋值上去。
    <p>Hello, my name is Krasimir. I'm 29 years old.</p>
    

    这是我们想到了强大的正则,exec方法来遍历和替换。
    这句正则表达式会捕获所有以<%开头,以%>结尾的片段。

    var re = /<%([^%>]+)?%>/g;
    var match = re.exec(tpl);
    

    [
    "<%name%>",
    " name ",
    index: 21,
    input:
    "<p>Hello, my name is <%name%>. I'm <%age%> years old.</p>"
    ]
    有了上面的思路,我们很快就可以开始了。

    var TemplateEngine = function(tpl, data) {
        var re = /<%([^%>]+)?%>/g;
        while(match = re.exec(tpl)) {
            tpl = tpl.replace(match[0], data[match[1]])
        }
        return tpl;
    }
    //output: <p>Hello, my name is Krasimir. I'm 29 years old.</p>
    

    问题来了,如果我给出的data是这种形式怎么办

    {
    name: "Krasimir Tsonev",
    profile: { age: 29 }
    }
    var template = '<p>Hello, my name is <%name%>. I'm <%profile.age%> years old.</p>';

    显然这个时候如果还用上面的那个方法,就会有问题。我们先来看一下这个

    var fn = new Function("arg", "console.log(arg + 1);");
    fn(2); // outputs 3
    

    相信有一定基础的同学见过这种写法。
    按照之前的想法,这个模板引擎最终返回的应该是一个编译好的模板。还是用之前的模板字符串作为例子,那么返回的内容应该类似于:

    return
    "<p>Hello, my name is " +
    this.name +
    ". I\'m " +
    this.profile.age +
    " years old.</p>";
    

    下一步就是收集模板里面不同的代码行,用于生成函数。

    var TemplateEngine = function(tpl, data) {
        var re = /<%([^%>]+)?%>/g,
            code = 'var r=[];\n', // 存放代码的变量
            cursor = 0;
        var add = function(line) {
            code += 'r.push("' + line.replace(/"/g, '\\"') + '");\n';
        }
        while(match = re.exec(tpl)) {
            add(tpl.slice(cursor, match.index)); // 这个代码是没有< %  的部分
            add(match[1]);  // 这个收集的变量
            cursor = match.index + match[0].length;  // cursor会增加
        }
        add(tpl.substr(cursor, tpl.length - cursor));
        code += 'return r.join("");'; // <-- return the result
        console.log(code);
        return tpl;
    }
    

    所以我们放心大胆的去运行下面这段代码

    var template = '<p>Hello, my name is <%this.name%>. I\'m <%this.profile.age%> years old.</p>';
    console.log(TemplateEngine(template, {
        name: "Krasimir Tsonev",
        profile: { age: 29 }
    }));
    

    var r=[];
    r.push("<p>Hello, my name is ");
    r.push("this.name");
    r.push(". I'm ");
    r.push("this.profile.age");
    return r.join("");

    问题来了,这个this.name 根本不需要引号,它本身是代码。
    this.name和this.profile.age不应该有引号啊,再来改改。

    var add = function(line, js) {
        js? code += 'r.push(' + line + ');\n' :
            code += 'r.push("' + line.replace(/"/g, '\\"') + '");\n';
    }
    while(match = re.exec(tpl)) {
        add(tpl.slice(cursor, match.index));
        add(match[1], true); // <-- say that this is actually valid js
        cursor = match.index + match[0].length;
    }
    

    所以最后通过这样一句代码返回即可

    var TemplateEngine = function(tpl, data) {
        var re = /<%([^%>]+)?%>/g,
            code = 'var r=[];\n', // 存放代码的变量
            cursor = 0;
        ``` 省略
        add(tpl.substr(cursor, tpl.length - cursor));
        code += 'return r.join("");'; // <-- return the result
        console.log(code);
        return new Function(code.replace(/[\r\t\n]/g, '')).apply(data);
    

    模板引擎接近完成了,不过还有一点,我们需要支持更多复杂的语句,比如条件判断和循环。我们接着上面的例子继续写。

    var template =
    'My skills:' +
    '<%for(var index in this.skills) {%>' +
    '<a href="#"><%this.skills[index]%></a>' +
    '<%}%>';
    console.log(TemplateEngine(template, {
    skills: ["js", "html", "css"]
    }));

    这里按照之前的写法,会有问题,对应的for循环不应被当作字符串加进数组里。

    var r=[];
    r.push("My skills:");
    r.push(for(var index in this.skills) {);  这里不需要push进数组
    r.push("<a href=\"\">");
    r.push(this.skills[index]);
    r.push("</a>");
    r.push(});
    r.push("");
    return r.join("");
    

    带有for循环的那一行不应该被直接放到数组里面,而是应该作为脚本的一部分直接运行。所以我们在把内容添加到code变量之前还要多做一个判断。

    var re = /<%([^%>]+)?%>/g,
        reExp = /(^( )?(if|for|else|switch|case|break|{|}))(.*)?/g,
        code = 'var r=[];\n',
        cursor = 0;
    var add = function(line, js) {
        js? code += line.match(reExp) ? line + '\n' : 'r.push(' + line + ');\n' :
            code += 'r.push("' + line.replace(/"/g, '\\"') + '");\n';
    }
    

    这里我们新增加了一个正则表达式。它会判断代码中是否包含if、for、else等等关键字。如果有的话就直接添加到脚本代码中去,否则就添加到数组中去。运行结果如下:

    var r=[];
    r.push("My skills:");
    for(var index in this.skills) {
    r.push("<a href="#">");
    r.push(this.skills[index]);
    r.push("</a>");
    }
    r.push("");
    return r.join("");
    //My skills:<a href="#">js</a><a href="#">html</a><a href="#">css</a>
    最后一个改进可以使我们的模板引擎更为强大。我们可以直接在模板中使用复杂逻辑,例如:

    var TemplateEngine = function(html, options) {
        var re = /<%([^%>]+)?%>/g, reExp = /(^( )?(if|for|else|switch|case|break|{|}))(.*)?/g, code = 'var r=[];\n', cursor = 0;
        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 += 'return r.join("");';
        return new Function(code.replace(/[\r\t\n]/g, '')).apply(options);
    }
    

    总共只用了15行代码就实现了一个简单的模版引擎,大家有兴趣可以自己去实现更为复杂的。

    相关文章

      网友评论

          本文标题:自定义模版引擎

          本文链接:https://www.haomeiwen.com/subject/mqgylxtx.html