Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

javascript模板引擎 #19

Open
damoclesX opened this issue Dec 8, 2015 · 0 comments
Open

javascript模板引擎 #19

damoclesX opened this issue Dec 8, 2015 · 0 comments
Labels

Comments

@damoclesX
Copy link
Owner

javascript模板引擎

之前使用过的模板引擎有:ejs、handlebars。能提升不少工作效率,不用去具体操作dom,只关心数据的获取,然后把渲染好的模板再一股脑儿插入到dom中。

网上也看到很多说几十行就实现模板引擎的文章,原理清楚,不过一直没有亲自实践过(纸上得来终觉浅啊),虽然现在我几乎没用前端的模板引擎了。今日闲来无事,也来把玩把玩。

想要知道怎么写,首先得清楚要怎么用?假设我有这么一段模板内容(js字符串,或script[type != javascript]标签,或新元素template),如下:

这里模板分割符采用<% %>

<h1>你好,<%= name %></h1>

那我在js里面这样调用

var tplString = '<h1>你好,<%= name %></h1>';

function Template(tpl){
    //todo
}
var tpl  = new Template( tplString );
var res = tpl.render( { name: 'damocles' } );
// <h1>你好,damocles</h1>

如果只是针对这个例子,采用简单替换就可以了,这里不作介绍,因为这种情况,现实应用中确实太少了。现实运用中,最好有简单的js执行逻辑,比如:if,else,for之类,数据不仅是支持基本类型,还可以有复杂类型:object,array({menu: ['菜单1','菜单2','菜单3'] }),不过调用就是这样。

先只处理这两种情况
<%- jscode %> js执行代码
<%= variable %> 输出变量

function TemplateEngine (tpl){

       var reg = /\<\%([-=]+)([^\%]+)\%\>/m;//匹配模板字符串中的<%[-=] %>的内容
       var match;
       var code = ['var r = [];'];//搜集模板代码内容,

        //循环匹配
        while(match = reg.exec(tpl)){
            if(match.index>=0){
                //<% %>之前的字符串,replace用于模板字符串中包含双引号的情况,放置代码解析失败
                code.push('\tr.push("'+tpl.substring(0, match.index).replace('"', '\\\"')+'")')
                //js代码的情况
                if(match[1] == '-'){
                    code.push(match[2])
                //输出变量的情况
                }else if(match[1] == '='){
                    code.push('\tr.push('+match[2]+')')
                }
            }
            //继续处理剩下的字符串,进入下一次循环
            tpl = tpl.substring(match.index+match[0].length)
        }

        code.push('return r.join(";")');//创建函数时,用;链接

        var fn = new Function(code.join('\n'));//创建函数,放在这里执行,预编译?

        this.render = function(data){
              fn.apply(data)
        };
}

采用上面这中代码,有一定限制,在渲染模板的时候,变量为this[变量名],参考其他文章的做法,有些是在循环匹配阶段手动添加"this.",这种限制比较大,要考虑在什么情况下添加,有些是在使用模板代码时,在模板里面加this,这种其实比较灵活,但是会比较繁琐,每个变量前面都需要加this。

一个完整的例子

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>javascript模板引擎</title>
</head>
<body>
    <div id="tpl"></div>
    <script type="text/tpl">
        <%- for(var i=0;i<this.menu.length;i++){ %>
            <p><a href="<%= this.menu[i].url %>"><%= this.menu[i].name %></a></p>
        <%- } %>

        <h1>javascript模板</h1>
        <%- if(this.logined){%>
            <%= this.name %>
        <%- }%>
    </script>
    <script>
    function Template(tpl){
        tpl = tpl.replace(/[\n\r\t]/g, '').replace(/[\s\t]+/g,' ');//替换模板字符串中的换行
        var reg = /\<\%([-=]+)([^\%]+)\%\>/m;

        var code = ['var r = [];'];

        while(match = reg.exec(tpl)){
            if(match.index>=0){
                code.push('\tr.push("'+tpl.substring(0, match.index).replace('"', '\\\"')+'")')
                if(match[1] == '-'){
                    code.push(match[2])
                }else if(match[1] == '='){
                    code.push('\tr.push('+match[2]+')')
                }
            }
            tpl = tpl.substring(match.index+match[0].length)
        }

        code.push('return r.join("")');
        var fn = new Function(code.join('\n;'));

        this.render = function(model) {
            return fn.apply(model)
        };
    }
    var tpl = new Template(document.querySelector('script[type="text/tpl"]').innerHTML);

    document.getElementById('tpl').innerHTML = tpl.render({menu: [
        {
            name: '菜单1',
            url: 'www.baidu.com'
        },{
            name: '菜单2',
            url: 'www.qq.com'
        }
    ],logined: true, name:'damocles'});
    </script>
</body>
</html>

这里采用了在模板代码加this的方式,但是觉得感觉好繁琐。想在搜集模板代码的时候,自动添加this.,尝试无果(可能是我没有找到方法吧),于是想:我如果在函数创建时,把渲染模板时的数据当作参数传进去,这种可以实现。

模板代码中省去this的完整例子

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>javascript模板引擎</title>
</head>
<body>
    <div id="tpl"></div>
    <script type="text/tpl">
        <%- for(var i=0;i<menu.length;i++){ %>
            <p><a href="<%= menu[i].url %>"><%= menu[i].name %></a></p>
        <%- } %>

        <h1>首页</h1>
        <%- if(logined){%>
            <%= name %>
        <%- }%>
    </script>
    <script>
    function Template(tpl){
        tpl = tpl.replace(/[\n\r\t]/g, '').replace(/[\s\t]+/g,' ');
        var reg = /\<\%([-=]+)([^\%]+)\%\>/m;

        var code = ['var r = [];'];

        while(match = reg.exec(tpl)){
            if(match.index>=0){
                code.push('\tr.push("'+tpl.substring(0, match.index).replace('"', '\\\"')+'")')
                if(match[1] == '-'){
                    code.push(match[2])
                }else if(match[1] == '='){
                    code.push('\tr.push('+match[2]+')')
                }
            }
            tpl = tpl.substring(match.index+match[0].length)
        }

        code.push('return r.join("")');
        //此时还不知传入的数据结构,无法在这里创建函数,没了预编译,速度是否变慢?
        this.render = function(model) {
            //渲染模板时搜集数据的keys和values
            var keys = [];
            var values = [];
            for(var i in model){
                keys.push(i);
                values.push(model[i]);
            }
            //函数主体
            keys.push(code.join('\n;'));
            //采用new Function方式无法传入无知数量、未知参数名的参数,只得调用Function的apply方法来创建函数
            var fn = Function.apply(this, keys);
            //调用函数,将数据的属性作为变量名传入
            return fn.apply(model, values)
        };
    }
    var tpl = new Template(document.querySelector('script[type="text/tpl"]').innerHTML);

    document.getElementById('tpl').innerHTML = tpl.render({menu: [
        {
            name: '菜单1',
            url: 'www.baidu.com'
        },{
            name: '菜单2',
            url: 'www.qq.com'
        }
    ],logined: true, name:'damocles'});
    </script>
</body>
</html>

采用这种方法,就能够省去模板代码中的this,当然这里使用this也不会报错,同样可用。只是不会在new Template的时候创建函数,没有了预编译,只在渲染的时候才创建函数,可能会影响到速度?

本来在搜集模板代码内容时候,想直接使用字符串拼接的方式,这样比数组push的方式快一点儿,只是可读性会差很多。

@damoclesX damoclesX added the js label Dec 8, 2015
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

1 participant