可是这还不够好,数据是很是简单的对象,而且很容易使用object['property']对象的中括号语法,去读取对象的值。css
但在实践中,咱们用到的数据中,可能有复杂的嵌套对象。html
//嵌套对象 data = { name: "Krasimir Tsonev", profile: {age:29} }
若是有复杂的嵌套对象,就不能用对象的中括号语法读取值了。正则表达式
因此String.prototype.replace(matchedStr, data["profile.age"]) 就行不通了。segmentfault
由于data["profile.age"],每次返回undefined。数组
//对象的中括号语法读取值 object["property"] var obj = { name: "Shaw", age: 18 } console.log(obj["name"]); //"Shaw" console.log(obj["age"]); // 18 //复杂的嵌套对象,就不能用对象的中括号语法读取值了。 var obj = { name: "Shaw", profile: { age: 18 } } console.log(obj["profile.age"]); // undefined
那么,怎么解决这个问题?
最好的办法是在模板中<%和%>之间放置真正的JavaScript代码。app
var tpl = '<p>Hello, my name is <%this.name%>. I\'m <%this.profile.age%> years old.</p>';
这怎么可能呢? John使用了new Function()语法, 没有显式的声明函数。函数
var fn = new Function("arg", "console.log(arg+1);"); fn(2); // 3 //fn是一个能够传入一个参数的函数 //fn函数体内的语句,就是 console.log(arg+1); /* 等价于*/ function fn(arg) { console.log(arg +1); } fn(2); //3
咱们能够利用这个语法,在一行代码中,定义函数、参数和函数体。这正是咱们须要的。优化
在利用这种语法,建立函数以前。this
咱们必须设计好,函数体怎么写。函数执行后应该返回最终编译的模板。prototype
回想一下,咱们常常使用的字符窜拼接方法。
"<p>Hello, my name is " + this.name + ". I\'m" + this.profile.age + " years old.</p>";
这说明,咱们能够把字符窜模板里面的内容拆解,拆解为html和JavaScript代码。
通常状况下,咱们都是使用for循环去遍历数据。
// 传入的字符窜模块 var template = 'My Skill:' + '<%for(var index in this.skills) {%>' + '<a href=""><%this.skills[index]%></a>' + '<%}%>';
// 预想的返回结果 return 'My skills:' + for(var index in this.skills) { + '<a href="">' + this.skills[index] + '</a>' + }
固然,这将产生一个错误。这就是为何我决定遵循约翰文章中使用的逻辑,把全部的字符串放到一个数组中。
var r = []; r.push('My skills:'); for(var index in this.skills) { r.push('<a href="">'); r.push(this.skills[index]); r.push('</a>'); } return r.join('');
下一个逻辑步骤是收集定制生成函数的不一样行。
咱们已经从模板中提取了一些信息。咱们知道占位符的内容和它们的位置。因此,经过使用一个辅助变量(游标)
function TemplateEngine(tpl, data) { var tplExtractPattern = /<%([^%>]+)?%>/g, code = 'var r=[];\n', cursor = 0, match; function addCode(line) { code += 'r.push("' + line.replace(/"/g,'\\"') +'"); \n'; } while(match = tplExtractPattern.exec(tpl)) { addCode(tpl.slice(cursor, match.index)); addCode(match[1]); cursor = match.index + match[0].length; } code += 'return r.join("");'; console.log(code); return tpl; } var template = '<p>Hello, my name is <%this.name%>. I\'m <%this.profile.age%> years old.</p>'; TemplateEngine(template, { name: "Shaw", profile: { age: 18 } });
变量code保存着函数体的代码。
在while循环语句中,咱们也须要变量cursor游标,告诉咱们字符窜slice()方法截取的起始坐标和末尾坐标。
变量code在while循环语句中,不断的拼接。
可是code的最终结果是
/* 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(""); <p>Hello, my name is <%this.name%>. I'm <%this.profile.age%> years old.</p> */
这不是咱们想要的。 "this.name" 和 "this.profile.name" 不该该被引号包裹。
因此咱们须要addCode函数作一个小小的改动
function TemplateEngine(tpl,data){ var tplExtractPattern = /<%([^%>]+)?%>/g, code = 'var r=[];\n', cursor = 0, match; function addCode(line, js) { if(js) { code += 'r.push(' + line + ');\n'; } else { code += 'r.push("' + line.replace(/"/g,'\\"') + '");\n'; } } while(match = tplExtractPattern.exec(tpl)) { addCode(tpl.slice(cursor, match.index)); addCode(match[1], true); cursor = match.index + match[0].length; } code += 'return r.join("");'; console.log(code); return tpl; } var template = '<p>Hello, my name is <%this.name%>. I\'m <%this.profile.age%> years old.</p>'; TemplateEngine(template, { name: "Shaw", profile: { age: 18 } });
如今this能够正确指向执行对象了。
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("");
到了这里,咱们只须要建立函数并执行它。
在TemplateEngine函数里把return tpl 替换成
return new Function(code.replace(/[\r\t\n]/g, '')).apply(data);
咱们不须要传入参数,这里我使用apply()方法,改变了做用域,如今this.name指向了data。
几乎已经完成了。可是咱们还须要支持更多JavaScript关键字,好比if/else,循环流程语句。
让咱们从相同的例子,再次进行构思。
function TemplateEngine(tpl,data){ var tplExtractPattern = /<%([^%>]+)?%>/g, code = 'var r=[];\n', cursor = 0, match; function addCode(line, js) { if(js) { code += 'r.push(' + line + ');\n'; } else { code += 'r.push("' + line.replace(/"/g,'\\"') + '");\n'; } } while(match = tplExtractPattern.exec(tpl)) { addCode(tpl.slice(cursor, match.index)); addCode(match[1], true); cursor = match.index + match[0].length; } code += 'return r.join("");'; console.log(code); return new Function(code.replace(/[\t\n\t]/g, '')).apply(data); } var template = 'My skill:' + '<%for(var index in this.skills) {%>' + '<a href="#"><%this.skills[index]%></a>' + '<%}%>'; TemplateEngine(template, { skills: ["js", "html", "css"] }); // Uncaught SyntaxError: Unexpected token for
调用TemplateEngine(),控制台报错了,Uncaught SyntaxError: Unexpected token for。
在控制台打印出,拼接的代码
var r=[]; r.push("My skill:"); r.push(for(var index in this.skills) {); r.push("<a href=\"#\">"); r.push(this.skills[index]); r.push("</a>"); r.push(}); return r.join("");
带有for循环的语句不该该被直接放到数组里面,而是应该做为脚本的一部分直接运行。因此在把代码语句添加到code变量以前还要多作一个判断。
var re = /<%([^%>]+)?%>/g, reExp = /(^( )?(if|for|else|switch|case|break|{|}))(.*)?/g, code = 'var Arr = [];\n', cursor = 0; function addCode(line,js) { if(js){ if(line.match(reExp)) { code += line +'\n'; } else { code += 'r.push(' + line + ');\n'; } } else { code += 'r.push("' + line.replace(/"/g,'\\"')) + '");\n'; } }
添加一个新的正则表达式。它会判断代码中是否包含if、for、else等关键字。
若是有的话就直接添加到脚本代码中去,不然就添加到数组中去。
function TemplateEngine(tpl,data){ var tplExtractPattern = /<%([^%>]+)?%>/g, jsExtractReExp = /(^( )?(if|for|else|switch|case|break|{|}))(.*)?/g, code = 'var arr = [];\n', cursor = 0, match; function addCode(line,js) { if(js){ if(line.match(jsExtractReExp)) { code += line +'\n'; } else { code += 'arr.push(' + line + ');\n'; } } else { code += 'arr.push("' + line.replace(/"/g,'\\"') + '");\n'; } } while(match = tplExtractPattern.exec(tpl)) { addCode(tpl.slice(cursor, match.index)); addCode(match[1], true); cursor = match.index + match[0].length; } code += 'return arr.join("");'; console.log(code); return new Function(code.replace(/[\t\n\t]/g, '')).apply(data); } var template = 'My skill:' + '<%for(var index in this.skills) {%>' + '<a href="#"><%this.skills[index]%></a>' + '<%}%>'; TemplateEngine(template, { skills: ["js", "html", "css"] }); /* var arr = []; arr.push("My skill:"); for(var index in this.skills) { arr.push("<a href=\"#\">"); arr.push(this.skills[index]); arr.push("</a>"); } return arr.join(""); */ //"My skill:<a href="#">js</a><a href="#">html</a><a href="#">css</a>"
一切都是正常编译的 :)。
最后的修改,实际上给了咱们更强大的处理能力。
咱们能够直接将复杂的逻辑应用到模板中。例如
var template = 'My skills:' + '<%if(this.showSkills) {%>' + '<%for(var index in this.skills) {%>' + '<a href="#"><%this.skills[index]%></a>' + '<%}%>' + '<%} else {%>' + '<p>none</p>' + '<%}%>'; console.log(TemplateEngine(template, { skills: ["js", "html", "css"], showSkills: true })); /* var arr = []; arr.push("My skills:"); if(this.showSkills) { arr.push(""); for(var index in this.skills) { arr.push("<a href=\"#\">"); arr.push(this.skills[index]); arr.push("</a>"); } arr.push(""); } else { arr.push("<p>none</p>"); } return arr.join(""); */ //"My skills:<a href="#">js</a><a href="#">html</a><a href="#">css</a>"
最后,我进一步作了一些优化,最终版本以下
var TemplateEngine = function(templateStr, data) { var tplStrExtractPattern = /<%([^%>]+)?%>/g, jsKeyWordsExtractPattern = /(^( )?(for|if|else|swich|case|break|{|}))(.*)?/g, code = 'var arr = [];\n', cursor = 0, match; var addCode = function(templateStr, jsCode) { if(jsCode) { if(templateStr.match(jsKeyWordsExtractPattern)) { code += templateStr + '\n'; } else { code += 'arr.push(' + templateStr + ');\n'; } } else { code += 'arr.push("' + templateStr.replace(/"/g, '\\"') + '");\n'; } } while(match = tplStrExtractPattern.exec(templateStr)){ addCode(templateStr.slice(cursor, match.index)); addCode(match[1], true); cursor = match.index + match[0].length; } code += 'return arr.join("");'; return new Function(code.replace(/[\r\n\t]/g, '')).apply(data); }