js模板引擎渐进--代码改进封装(9)

 

 

到目前为止,一个 简单的模板引擎 差不多成型了,但目前还有几个较大的问题:

1.代码复用。代码中有些地方还可以再精简一些

2.多个 左花括号 {  和 多个 右花括号  }  的情况处理

 

首先,对于第一个问题,我们可以翻开上一篇的代码,我们来改进一下:

a.有两个地方对 bracecount 取余然后处理 { 或者 } ,两个地方除了处理的后面一个 记录新的 brace 一个置空 brace,其它的处理是一样的,所以我们将这部分抽出来:

 var handlerBrace = function () {
                var remainder = bracecount % 2;
                if (remainder == 1) {
                    // bracecount/2 次的插入
                    for (var i = 0; i < parseInt(bracecount / 2); i++) {
                        handlerPushValue(brace);
                    }
                    handlerSwitch(brace)
                }
                else {
                    // bracecount/2 次的插入
                    for (var i = 0; i < parseInt(bracecount / 2); i++) {
                        handlerPushValue(brace);
                    }
                }
            }

然后,我们把   for (var key in template) {...}...return resultcode.join("");   这段代码,也用一个 function 包起来:

var handlerGetFuncCode = function (template) {
                resultcode=[];
                resultcode.push("function func(data) { var result=[];");

                for (var key in template) {
                    var value = template[key];
                    //if(value=='2') debugger;
                    //var switchvalue = "";
                    if (value == '{' || value == '}') {
                        if (brace == "") {
                            brace = value;
                            bracecount = 1;
                        }
                        else {
                            if (brace === value) {
                                bracecount++;
                            }
                            else {
                                handlerBrace();

                                brace = value;
                                bracecount = 1;
                            }
                        }
                    }
                    else {

                        if (bracecount > 0) {
                            handlerBrace();
                            brace = "";
                            bracecount = 0;
                        }
                        handlerSwitch(value)
                    }
                }
                resultcode.push(" result.push(\"" + tempcode.join("") + "\") ;");
                tempcode = [];

                resultcode.push("return result.join(\"\");}")
                return resultcode.join("");
            }

而对于我们之前   获取模板内容,解析内容,传递参数 这几个部分,我们也抽出来,形成一个只传入模板代码和数据,返回渲染结果的 function :

 var rendData = function (template, data) {
                var templatecode = handlerGetFuncCode(template);
                //console.log(templatecode)
                var func = context.eval("(" + templatecode + ")");
                var result = func(data);
                return result;
            }

最后,我们使用我们第一篇提到的 直接执行的匿名函数 (function(){})()  方式将整个 template2code 包装一下(这个 template2code 在这之前其实已经有了封装的影子了,我们把变量和复用的方法放到了同一个闭包环境里,使他们不受外界影响,但是,我们终究是要在外面使用的,所以我们进行封装一下),形成的代码如下:

(function Template2code(context) {


            var resultcode = [];
            var tempcode = [];
            var varcode = [];
            var isvarcode = false;
            var brace = "";
            var bracecount = 0;

            var handlerPushValue = function (value) {
                if (isvarcode) {
                    varcode.push(value.replace("\\\"", "\""))
                }
                else {
                    tempcode.push(value);

                }
            }

            var handlerBrace = function () {
                var remainder = bracecount % 2;
                if (remainder == 1) {
                    // bracecount/2 次的插入
                    for (var i = 0; i < parseInt(bracecount / 2); i++) {
                        handlerPushValue(brace);
                    }
                    handlerSwitch(brace)
                }
                else {
                    // bracecount/2 次的插入
                    for (var i = 0; i < parseInt(bracecount / 2); i++) {
                        handlerPushValue(brace);
                    }
                }
            }


            var handlerSwitch = function (switchvalue) {
                //console.log(value);
                switch (switchvalue) {
                    case "\n":

                        handlerPushValue(" ");

                        break;
                    case "\"":
                        handlerPushValue("\\\"");

                        break;
                    case "{":
                        isvarcode = true;
                        resultcode.push(" result.push(\"" + tempcode.join("") + "\") ;");
                        tempcode = [];
                        break;
                    case "}":
                        isvarcode = false;
                        var varvalue = varcode.join("");
                        if (varvalue.indexOf("each") == 0) {
                            // each index,value arr  
                            var eachInfo = varvalue.split(' ');
                            if (eachInfo.length != 3) throw "each 参数无效:each index,value arr";
                            var itemInfo = eachInfo[1].split(',');
                            if (itemInfo.length != 2) throw "each 参数无效:each index,value arr";

                            resultcode.push("for (var " + itemInfo[0] + " in " + eachInfo[2] + ") {var " + itemInfo[1] + " = " + eachInfo[2] + "[" + itemInfo[0] + "];");

                        }
                        else if (varvalue === "/each") {
                            resultcode.push(" }");
                        }
                        else if (varvalue.indexOf("if") == 0) {
                            var ifInfo = varvalue.split(" ");
                            resultcode.push("if(" + ifInfo[1] + "){");
                        }
                        else if (varvalue.indexOf("else-if") == 0) {
                            var ifInfo = varvalue.split(" ");
                            resultcode.push(" } else if(" + ifInfo[1] + "){");
                        }
                        else if (varvalue.indexOf("else") == 0) {
                            var ifInfo = varvalue.split(" ");
                            resultcode.push(" } else {");
                        }
                        else if (varvalue.indexOf("/if") == 0) {
                            resultcode.push(" }");
                        }
                        else {
                            resultcode.push(" result.push(" + varvalue + ") ;");
                        }
                        varcode = [];

                        break;

                    default:
                        handlerPushValue(switchvalue);

                }
            }

            var handlerGetFuncCode = function (template) {
                resultcode=[];
                resultcode.push("function func(data) { var result=[];");

                for (var key in template) {
                    var value = template[key];
                    //if(value=='2') debugger;
                    //var switchvalue = "";
                    if (value == '{' || value == '}') {
                        if (brace == "") {
                            brace = value;
                            bracecount = 1;
                        }
                        else {
                            if (brace === value) {
                                bracecount++;
                            }
                            else {
                                handlerBrace();

                                brace = value;
                                bracecount = 1;
                            }
                        }
                    }
                    else {

                        if (bracecount > 0) {
                            handlerBrace();
                            brace = "";
                            bracecount = 0;
                        }
                        handlerSwitch(value)
                    }
                }
                resultcode.push(" result.push(\"" + tempcode.join("") + "\") ;");
                tempcode = [];

                resultcode.push("return result.join(\"\");}")
                return resultcode.join("");
            }

            var rendData = function (template, data) {
                var templatecode = handlerGetFuncCode(template);
                //console.log(templatecode)
                var func = context.eval("(" + templatecode + ")");
                var result = func(data);
                return result;
            }

            context.JSTemplate = {
                rendData: rendData,
                handlerGetFuncCode: handlerGetFuncCode
            }
        })(window)

ps: "template2code" 这个方法名可以不要也行,不影响。最后context.JSTemplate 指向的对象中 handlerGetFuncCode 主要是为了测试输出,这个也不一定对外公开。

这样,我们对这个 template 进行了封装,然后我们就可以这样使用:

            var templatecontent = document.getElementById("template").innerHTML;

            var data = ["dddddd", "fffff", "gggggg", "hhhhh"]

            var result = JSTemplate.rendData(templatecontent, data);
            console.log(result);

接下来,我们还要 解决最上面我们提到的 情况 2 .

在上一篇中,我们的 花括号的数量都是理想化 两个两个 的成对使用的,那要是我出现类似的 {{{{{varcode}}}}} 这种情况,我们怎么处理呢?

如果按照上面的代码,也是可以处理,但是上面的代码会解析成:

result.push("{{") ; result.push(varcode}}) ;   

而我们其实是想要这样的效果:

result.push("{{") ; result.push(varcode) ;result.push("}}") ;

对于前者,输出的代码肯定是无法转成一个function,会报错。那我们该怎么处理呢?

我们来想想,括号它有左括号和右括号,是相对的,但是在我们前面的代码中,无论是 {  还是 }  ,我们都按照“先转为字符串内容,再将余出来的一个当作 指令结束符”,所以导致了解析出现了问题。目前是这样的:

var handlerBrace = function () {

                // bracecount/2 次的插入
                for (var i = 0; i < parseInt(bracecount / 2); i++) {
                    handlerPushValue(brace);
                }

                var remainder = bracecount % 2;
                if (remainder == 1) {

                    handlerSwitch(brace)
                }

            }

 

知道问题在哪了,改进就简单了,我们只需要改进 handlerBrace 函数,多判断一下,遇到 {  我们先将双数的部分改成字符串内容,再处理  {,而遇到  }  时,我们先处理 },然后再处理双数的 } 为字符串内容,改进后如下:

var handlerBrace = function () {



                var remainder = bracecount % 2;
                if (remainder == 1) {
                    // 左括号的 先当字符后当代码块
                    // 右括号的 先当代码块再当字符串
                    if (brace == '{') {
                        for (var i = 0; i < parseInt(bracecount / 2); i++) {
                            handlerPushValue(brace);
                        }
                        handlerSwitch(brace)
                    }
                    else {
                        handlerSwitch(brace)
                        for (var i = 0; i < parseInt(bracecount / 2); i++) {
                            handlerPushValue(brace);
                        }

                    }
                }
                else {
                    // bracecount/2 次的插入
                    for (var i = 0; i < parseInt(bracecount / 2); i++) {
                        handlerPushValue(brace);
                    }
                }

            }

到这里,我们开头提到的两个问题算是解决了,目前为止封装的功能能满足了大部分正常的需求了。但是我们还要继续改进一下我们的代码:

1.为了提高效率,我希望某个模板第一次渲染时需要重新生成,之后就不需要了。

这个我们将对模板内容求一个 哈希,然后将生成的 function 保存起来:

 var funcCache={}
var getHashCode = function (str, caseSensitive) {
                if (!caseSensitive) {
                    str = str.toLowerCase();
                }
                // 1315423911=b'1001110011001111100011010100111'
                var hash = 1315423911, i, ch;
                for (i = str.length - 1; i >= 0; i--) {
                    ch = str.charCodeAt(i);
                    hash ^= ((hash << 5) + ch + (hash >> 2));
                }

                return (hash & 0x7FFFFFFF);
            }

            var rendData = function (template, data) {
                var hascode = getHashCode(template);
                var func = funcCache[hascode];
                //console.log("funcCache[hascode]");
                //console.log(func)
                if (typeof func != "function") {
                    //console.log('nothing');
                    var templatecode = handlerGetFuncCode(template);
                    //console.log(templatecode)
                    func = context.eval("(" + templatecode + ")");
                    funcCache[hascode] = func;
                }

                var result = func(data);
                return result;
            }

这样同一个模板在异步中需要重用的话,第一次解析后将不再进行解析,提高效率。

 

2.将 eval 改成 Function

使用 Function 同样可以将function字符串生成 可执行的 function,而且还省了 写方法体(至于效率,自己没亲自测试,不好说话,就是觉得这里我用 Function 会好一些),改之后,完整代码如下:

 

 (function Template2code(context) {

            var funcCache = {};
            var resultcode = [];
            var tempcode = [];
            var isvarcode = false;
            var brace = "";
            var bracecount = 0;

            var handlerPushValue = function (value) {
                
                if (isvarcode) {
                    value = value.replace("\\\"", "\"");
                }
                tempcode.push(value);
            }

            var handlerBrace = function () {



                var remainder = bracecount % 2;
                if (remainder == 1) {
                    // 左括号的 先当字符后当代码块
                    // 右括号的 先当代码块再当字符串
                    if (brace == '{') {
                        for (var i = 0; i < parseInt(bracecount / 2); i++) {
                            handlerPushValue(brace);
                        }
                        handlerSwitch(brace)
                    }
                    else {
                        handlerSwitch(brace)
                        for (var i = 0; i < parseInt(bracecount / 2); i++) {
                            handlerPushValue(brace);
                        }

                    }
                }
                else {
                    // bracecount/2 次的插入
                    for (var i = 0; i < parseInt(bracecount / 2); i++) {
                        handlerPushValue(brace);
                    }
                }

            }


            var handlerSwitch = function (switchvalue) {
                //console.log(value);
                switch (switchvalue) {
                    case "\n":

                        handlerPushValue(" ");
                        break;
                    case "\"":
                        handlerPushValue("\\\"");

                        break;
                    case "{":
                        isvarcode = true;
                        resultcode.push(" result.push(\"" + tempcode.join("") + "\") ;");
                        tempcode = [];
                        break;
                    case "}":
                        isvarcode = false;
                        var varvalue = tempcode.join("");
                        if (varvalue.indexOf("each") == 0) {
                            // each index,value arr  
                            var eachInfo = varvalue.split(' ');
                            if (eachInfo.length != 3) throw "each 参数无效:each index,value arr";
                            var itemInfo = eachInfo[1].split(',');
                            if (itemInfo.length != 2) throw "each 参数无效:each index,value arr";

                            resultcode.push("for (var " + itemInfo[0] + " in " + eachInfo[2] + ") {var " + itemInfo[1] + " = " + eachInfo[2] + "[" + itemInfo[0] + "];");

                        }
                        else if (varvalue === "/each") {
                            resultcode.push(" }");
                        }
                        else if (varvalue.indexOf("if") == 0) {
                            var ifInfo = varvalue.split(" ");
                            resultcode.push("if(" + ifInfo[1] + "){");
                        }
                        else if (varvalue.indexOf("else-if") == 0) {
                            var ifInfo = varvalue.split(" ");
                            resultcode.push(" } else if(" + ifInfo[1] + "){");
                        }
                        else if (varvalue.indexOf("else") == 0) {
                            var ifInfo = varvalue.split(" ");
                            resultcode.push(" } else {");
                        }
                        else if (varvalue.indexOf("/if") == 0) {
                            resultcode.push(" }");
                        }
                        else {
                            resultcode.push(" result.push(" + varvalue + ") ;");
                        }
                        tempcode = [];

                        break;

                    default:
                        handlerPushValue(switchvalue);

                }
            }

            var handlerGetFuncCode = function (template) {
                resultcode = [];
                resultcode.push(" var result=[];");

                for (var key in template) {
                    var value = template[key];
                    //if(value=='2') debugger;
                    //var switchvalue = "";
                    if (value == '{' || value == '}') {
                        if (brace == "") {
                            brace = value;
                            bracecount = 1;
                        }
                        else {
                            if (brace === value) {
                                bracecount++;
                            }
                            else {

                                handlerBrace();

                                brace = value;
                                bracecount = 1;
                            }
                        }
                    }
                    else {

                        if (bracecount > 0) {
                            handlerBrace();
                            brace = "";
                            bracecount = 0;
                        }
                        handlerSwitch(value)
                    }
                }
                resultcode.push(" result.push(\"" + tempcode.join("") + "\") ;");
                tempcode = [];

                resultcode.push("return result.join(\"\");")
                return resultcode.join("");
            }

            var getHashCode = function (str, caseSensitive) {
                if (!caseSensitive) {
                    str = str.toLowerCase();
                }
                // 1315423911=b'1001110011001111100011010100111'
                var hash = 1315423911, i, ch;
                for (i = str.length - 1; i >= 0; i--) {
                    ch = str.charCodeAt(i);
                    hash ^= ((hash << 5) + ch + (hash >> 2));
                }

                return (hash & 0x7FFFFFFF);
            }

            var rendData = function (template, data) {
                var hascode = getHashCode(template);
                var func = funcCache[hascode];
                //console.log("funcCache[hascode]");
                //console.log(func)
                if (typeof func != "function") {
                    //console.log('nothing');
                    var templatecode = handlerGetFuncCode(template);
                    //console.log(templatecode)
                    func = new Function("data",templatecode);
                    funcCache[hascode] = func;
                }

                var result = func(data);
                return result;
            }

            context.JSTemplate = {
                rendData: rendData,
                handlerGetFuncCode: handlerGetFuncCode
            }
        })(window)

最后来看一下效果:

模板代码:

<h3>总共:{{{data.users.length}}}</h3>
        <table>
            <tr>
                <th>序号</th>
                <th>姓名</th>
                <th>性别</th>
                <th>学号</th>
            </tr>
            {each index,value data.users}
            <tr {if index%2==1}style="background:#dddddd;"{/if}>
                <td>{Number(index)+1}</td>
                <td>{value.name}</td>
                <td>{value.gender==0?"男":"女"}</td>
                <td>{value.number}</td>
            </tr>
            {/each}
        </table>

传入数据:

var data = {
                users: [
                    {
                        name: "张三1",
                        gender: 0,
                        number: "1001"
                    },
                    {
                        name: "李四2",
                        gender: 1,
                        number: "1002"
                    },
                    {
                        name: "张三3",
                        gender: 0,
                        number: "1003"
                    },
                    {
                        name: "张三4",
                        gender: 0,
                        number: "1004"
                    },
                    {
                        name: "张三5",
                        gender: 0,
                        number: "1005"
                    },
                    {
                        name: "李四7",
                        gender: 1,
                        number: "1006"
                    }
                ]
            }

渲染结果:

<h3>总共:{6}</h3> <table> <tr> <th>序号</th> <th>姓名</th> <th>性别</th> <th>学号</th> </tr> <tr > <td>1</td> <td>张三1</td> <td>男</td> <td>1001</td> </tr> <tr style="background:#dddddd;"> <td>2</td> <td>李四2</td> <td>女</td> <td>1002</td> </tr> <tr > <td>3</td> <td>张三3</td> <td>男</td> <td>1003</td> </tr> <tr style="background:#dddddd;"> <td>4</td> <td>张三4</td> <td>男</td> <td>1004</td> </tr> <tr > <td>5</td> <td>张三5</td> <td>男</td> <td>1005</td> </tr> <tr style="background:#dddddd;"> <td>6</td> <td>李四7</td> <td>女</td> <td>1006</td> </tr> </table>

 

至此,这个template的代码也算是成型了。

 

目录:

js模板引擎渐进--前言

js模板引擎渐进--区别js代码和html字符串(1)

js模板引擎渐进--生成最简单的function(2)

js模板引擎渐进--区别js代码和html字符串2(3)

js模板引擎渐进--改进字符串拼接方式(4)

js模板引擎渐进--each/for(5)

js模板引擎渐进--each改进(6)

js模板引擎渐进--if/else(7)

js模板引擎渐进--处理需要输出 { 或者 } 的情况(8)

js模板引擎渐进--代码改进封装(9)

js模板引擎渐进--后记

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值