可是這還不夠好,數據是很是簡單的對象,而且很容易使用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); }