- 什么是模板引擎
- 概念 :模板引擎是為了使用戶界面與業(yè)務(wù)數(shù)據(jù)(內(nèi)容)分離而產(chǎn)生的,它可以生成特定格式的文檔,用于網(wǎng)站的模板引擎就會(huì)生成一個(gè)標(biāo)準(zhǔn)的文檔
就是將模板文件和數(shù)據(jù)通過模板引擎生成一個(gè)HTML代碼
image.png
- 概念 :模板引擎是為了使用戶界面與業(yè)務(wù)數(shù)據(jù)(內(nèi)容)分離而產(chǎn)生的,它可以生成特定格式的文檔,用于網(wǎng)站的模板引擎就會(huì)生成一個(gè)標(biāo)準(zhǔn)的文檔
-
如一個(gè)簡(jiǎn)單的需求
1.數(shù)據(jù)來自一個(gè)數(shù)組
2.不能寫死在頁面里
image.png
songs === [
{name: '紳士', url: 'http://music.163.com/xxx', singer: '薛之謙'},
{name: '剛剛好', url: 'http://music.163.com/yyy', singer: '薛之謙'},
...
]
兩種方法:1.遍歷拼接HTML字符串 2.遍歷構(gòu)造DOM對(duì)象
一個(gè)簡(jiǎn)化的模板引擎
字符串格式化
before:
var li = '<li>' + song[i].name + '-' + song[i].singer + '</li>'
after:
var li = stringFormat('<li>{0} - {1}</li>', songs[i].name, songs[i].singer)
function stringFormat(string){ // string是變量不是屬性,不能使用this
//params此時(shí)為string從1開始之后的數(shù)組
var params = [].slice.call(arguments,1) // arguments為偽數(shù)組,數(shù)組的方法不能使用 call將arguments設(shè)為this
var regex = /\{(\d+)\}/g //匹配{一個(gè)或多個(gè)數(shù)字}
//將字符串中的{n}替換為params[n]
string = string.replace(regex,function(){
//function的arguments[1]為字符串中的{n},arguments[0]為字符串中的{n}
var index = arguments[1]
console.log(arguments)
return params[index]
})//取代為function的返回值
return string
}
- 第一版模板引擎
模板文件 : var template = '<p>Hello, my name is <%name%>. I\'m <%age%> years old.</p>';
數(shù)據(jù): var data = {
name: "Krasimir",
age: 29
}
/*
regex.exec 正則,遍歷,匹配
exec在遍歷匹配的所有字符串,遍歷完后返回null,然后重新遍歷
[^%>]不是%或>任一個(gè)字符
()正則里分組的意思,分組就是我要用的意思,用括號(hào)里的東西
*/
模板引擎: var formatEngine = function (tpl,data){
var regex = /<%([^%>]+)?%>/g
while(match = regex.exec(tpl)){ //這里=是賦值
tpl = tpl.replace(match[0],data[match[1]])
console.log(tpl)
}
return tpl
}
HTML文件:
var string = formatEngine(template,data)
console.log(string)
- 復(fù)雜邏輯版模板引擎
預(yù)備知識(shí):
new Function:根據(jù)字符串創(chuàng)建一個(gè)函數(shù)
var fn = new Function("num","console.log(num + 1)"
fn(1)//2
var fn = new Function("num","console.log(num + '1')"
fn(1)//11
等價(jià)于
var fn = function(num){
console.log(num + 1);//函數(shù)體
}
fn(1) //2
- 通過這種方法,我們可以根據(jù)字符串構(gòu)造函數(shù),包括他的參數(shù)和函數(shù)體,模板引擎最終返回一個(gè)編譯好的模板,例如
return
"<p>Hello, my name is " +
this.name +
". I\'m " +
this.profile.age +
" years old.</p>";
把所有字符串放在一個(gè)數(shù)組里,在程序的最后把他們拼接起來
var arr = []
arr.push("<p>Hello, my name is " )
arr.push(this.name)
Arr.push("i am");
Arr.push(this.proflie.age)
Arr.push("years old</p>")
return arr.join('')
//模板文件
var template = '<p>Hello, my name is <%this.name%>. I\'m <%this.profile.age%> years old.</p>';
//數(shù)據(jù)
var data = {
name: "zyn",
profile: { age: 29 }
}
var re = /<%([^%>]+)?%>/g,
code = 'var Arr=[];\n',//code保存函數(shù)體
cursor = 0;//cursor游標(biāo)告訴我們當(dāng)前解析到了模板的哪個(gè)位置,我們需要它來遍歷整個(gè)模板字符串
//函數(shù)add,負(fù)責(zé)將解析出來的代碼行添加到變量code中去
//特別注意:把code包括的雙引號(hào)字符進(jìn)行轉(zhuǎn)義,否則代碼出錯(cuò)
/*
var func = new Function('x',"console.log(x+\"1\")")
func(1)
*/
var add = function(line){
//轉(zhuǎn)義
code += 'arr.push("' + line.relpace(/"/g,'\\"') + '");\n'
}
while(match = regex.exec(tpl)){
add(tpl.slice(cursor,match.index))
add(match[1])
cursor = match.index + match[0].length
}
第一次while循環(huán)時(shí)
match=[ 0:“<%this.name%>",
1:"this.name",
index:21,
input:"<p>Hello, my name is<%this.name%>.I'm<%this.profile.age%>years old.</p>",
length:2
]
=>tpl.slice(cursor,match.index) //<p>Hello, my name is
add(tpl.slice(cursor,match.index))
code=
"
var Arr=[];
Arr.push("<p>Hello, my name is ");
"
match[1] =>"this.name"
cursor = match.index + match[0].length //就是<%this.name%>最后一位的位置
然后依次類推循環(huán)
得到
var Arr[];
Arr.push("<p>Hello, my name is "); Arr.push("this.name");
Arr.push(". I'm ");
Arr.push("this.profile.age")
Arr.push("years old </p>")
return Arr.join("") <p>Hello, my name is <%this.name%>. I'm <%this.profile.age%> years old.</p>
this.name和this.profile.age不應(yīng)該有引號(hào)
var add = function(line,js){//改變參數(shù),占位符的內(nèi)容和布爾值做參數(shù)
//轉(zhuǎn)義
js?code += 'arr.push(' + line + ');\n':// 改動(dòng)1
code += 'arr.push("' + line.relpace(/"/g,'\\"') + '");\n'
}
while(match = regex.exec(tpl)){
add(tpl.slice(cursor,match.index))
add(match[1],true) //改動(dòng)2
cursor = match.index + match[0].length
}
}
改動(dòng)1:三目運(yùn)算,如果是js 就執(zhí)行 code += 'Arr.push(' + line + ');\n' 否則執(zhí)行 code += 'Arr.push("' + line.replace(/"/g, '\\"') + '");\n';
改動(dòng)2:add(match[1],true);告訴函數(shù)add這次傳入的是js.
剩下來是創(chuàng)建函數(shù)并執(zhí)行它
return new Function(code.replace(/[\r\t\n]/g,'')).apply(data);
不需要顯式的傳參數(shù)給函數(shù),使用apply方法來調(diào)用它,它會(huì)自動(dòng)設(shè)定函數(shù)執(zhí)行的上下文,this.name,這里this指向data對(duì)象
- 當(dāng)條件判斷和循環(huán)時(shí)
var template = 'My skills:' + '<%for(var index in this.skills) {%>' + '<a href=""><%this.skills[index]%></a>' + '<%}%>'; var skills= ["js", "html", "css"]
如果使用字符串拼接的話,代碼就應(yīng)該是下面的樣子:
var temolate='My skills:' +
for(var index in this.skills) { +
'<a href="">' +
this.skills[index] +
'</a>' +
}
這里會(huì)產(chǎn)生一個(gè)異常,Uncaught SyntaxError: Unexpected token for。如果我們調(diào)試一下,把code變量打印出來,我們就能發(fā)現(xiàn)問題所在。
var Arr=[];
Arr.push("My skills:");
Arr.push(for(var index in this.skills) {); Arr.push("<a href=\"\">");
Arr.push(this.skills[index]);
Arr.push("</a>");
Arr.push(});
Arr.push("");
return Arr.join("");
帶for循環(huán)的那一行不能直接放到數(shù)組里,而是作為腳本的一部分直接運(yùn)行,所以把內(nèi)容添加到code變量之前還要做一個(gè)判斷
最終代碼
var templateEngine = function(html,option){
var re = /<%([^%>]+)?%>/g,
reExp = /(^( )?(if|for|else|switch|case|break|{|}))(.*)?/g, //.*任意字符一次或多次
code = 'var r=[];\n',
cursor = 0;
var add = function(line,js){
js?(code += line.match(reExp) ? line + '\n' : 'r.push(' + line + ');\n'):
(code += line != '' ? 'r.push("' + line.replace(/"/g, '\\"') + '");\n' : '');
return add
}
while(match = re.exec(html)){
add(html.slice(cursor,match.index))
add(match[1],true)
cursor = match.index + match[0].length
}
add(html.substr(cursor,html.length - cursor)) //substr(start,length)
code += 'return r.join("")'
return new Function(code.replace(/[\r\t\n]/g, '')).apply(options);
}

