到目前为止,一个 简单的模板引擎 差不多成型了,但目前还有几个较大的问题:
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的代码也算是成型了。
目录: