要實(shí)現(xiàn)的目標(biāo)
<p>這是<%name%>的第一個(gè)<% content %></p>
data = {
name: 'a',
content: 'template'
}
變成
<p>這是a的第一個(gè)template</p>
這是個(gè)比較簡單的 html 解析代碼,用正則即可完成煌恢。思路是將所有的<% %>包裹的東西骇陈,與 data 對(duì)象的 key 匹配,匹配上替換即可瑰抵。
let templateDel = function(tpl, data) {
var result = tpl.replace(/<%\s*([^%>|\s]+)?\s*%>/g, function(s0, s1) {
return data[s1]
})
console.log(result)
}
檢驗(yàn)一下這個(gè)模板處理函數(shù)是否達(dá)到我們預(yù)期的效果
html
<body>
<div id="results">
</div>
<script type="text/html" id="item_tmpl">
<p>這是<%name%>的第一個(gè)<% age %></p>
<a href=""><%name%></a>
</script>
</body>
id="item_tmpl"
標(biāo)簽中包含著我們需要處理的模板語言你雌,處理完后放到id="results"
里
js
let templateDel = function(id, data, toWhere) {
let tpl = document.getElementById(id).innerHTML
var result = tpl.replace(/<%\s*([^%>|\s]+)?\s*%>/g, function(s0, s1) {
return data[s1]
})
console.log(result)
document.getElementById(toWhere).innerHTML = result
}
let data = {
name: 'a',
age: 'template'
}
templateDel('item_tmpl', data, 'results')
審查頁面的元素
<div id="results">
<p>這是a的第一個(gè)template</p>
<a href="">a</a>
</div>
這樣似乎滿足了我們一開始的需求,但是如果處理的數(shù)據(jù)比較復(fù)雜二汛,這樣似乎就不行了
<script type="text/html" id="item_tmpl">
<p>這是<%name%>的第一個(gè)<% age %></p>
<a href=""><%obj.x%></a>
</script>
let data = {
name: 'a',
age: 'template',
obj: {
x: 'xxxx'
}
}
templateDel('item_tmpl', data, 'results')
這時(shí)模板里的 obj.x 就被替換成 data[obj.x],也就是 undefined 了
而且模板中一般會(huì)有一些循環(huán)和條件判斷的處理婿崭,這樣寫也沒有辦法解析
<script type="text/html" id="item_tmpl">
<% for(var i=0; i< obj.arr.length; i++){ %>
<p>這是obj.arr的第<%i%>個(gè)<% obj.arr[i] %></p>
<% if(i != 2){ %>
<a href=""><%i%></a>
<%}%>
<%}%>
</script>
這時(shí)我們不能簡單的用正則匹配替換就可以,而是換一種思路习贫。即逛球,把模板語句 用 function 執(zhí)行千元。
所以我們需要做的就是把模板當(dāng)作字符串 分割開來苫昌,然后在對(duì)變量和函數(shù)來 處理后在拼接起來。
第一步:用正則將代碼的分割
let templateDel = function(id, data, toWhere) {
let tpl = document.getElementById(id).innerHTML
var reg = /<%\s*([^%>]+)?\s*%>/g
while ((match = reg.exec(tpl))) {
console.log(match)
// 這里match[1]是所有用了模板符號(hào)的代碼,那沒有用模板的標(biāo)簽或其他代碼怎么表示呢祟身?
}
}
第二步:用一個(gè)變量 cursor 來標(biāo)示當(dāng)前解析到了哪個(gè)位置奥务,可以稱之為 游標(biāo)
let templateDel = function(id, data, toWhere) {
let tpl = document.getElementById(id).innerHTML
var reg = /<%\s*([^%>]+)?\s*%>/g,
cursor = 0 // 默認(rèn)位置為0
while ((match = reg.exec(tpl))) {
console.log(match)
// 這里match[1]是所有用了模板符號(hào)的代碼
console.log(match.index)
// 當(dāng)前正則解析到的模板的開始位置
console.log(tpl.slice(cursor, match.index))
// 當(dāng)前正則解析到的模板之前的沒有用模板的標(biāo)簽或其他代碼
cursor = match.index + match[1].length
// 重新定位游標(biāo)位置
}
}
第三步:將分割完的代碼用數(shù)組存起來
這里不是簡單的定義一個(gè)變量類型為數(shù)組,然后往里 push袜硫,而是應(yīng)該定義一個(gè) 字符串氯葬, 該字符串應(yīng)是我們處理模板的函數(shù)體。
定義一個(gè) add 函數(shù)婉陷,專門處理 分割后的代碼往字符串里添加
let templateDel = function(id, data, toWhere) {
let tpl = document.getElementById(id).innerHTML
var reg = /<%\s*([^%>]+)?\s*%>/g,
cursor = 0, // 默認(rèn)位置為0
code = 'var r=[]; \n',
add = function(line) {
code += 'r.push("' + line.replace(/"/g, '\\"') + '");\n'
}
while ((match = reg.exec(tpl))) {
add(tpl.slice(cursor, match.index))
add(match[1])
cursor = match.index + match[1].length
// 重新定位游標(biāo)位置
}
add(tpl.substr(cursor, tpl.length - cursor)) // 這里不能忘了最后一個(gè)正則匹配后的非模板標(biāo)識(shí)符包含的代碼塊
}
第四步:完善 add 函數(shù)
我們處理分割后的字符串帚称,應(yīng)該區(qū)分對(duì)待
不在模板標(biāo)識(shí)符中的 以字符串形式,存儲(chǔ)在數(shù)組中
在模板標(biāo)識(shí)符中的變量 以變量形式存在數(shù)組中
在模板標(biāo)識(shí)符中的函數(shù)語句秽澳,如 for闯睹,if 等 不存在數(shù)組中,而是直接拼接在函數(shù)體中
let templateDel = function(id, data, toWhere) {
let tpl = document.getElementById(id).innerHTML
var reg = /<%\s*([^%>]+)?\s*%>/g,
cursor = 0, // 默認(rèn)位置為0
reExp = /(^( )?(if|for|else|switch|case|break|{|}))(.*)?/g, // 該正則是用來檢測是否是for担神,if等函數(shù)語句
code = 'var r=[]; \n',
add = function(line, js) {
// js為判斷是否是在模板標(biāo)識(shí)符中包含的代碼塊
js
? (code += line.match(reExp) ? line + '\n' : 'r.push(' + line + ');\n')
: (code += 'r.push("' + line.replace(/"/g, '\\"') + '");\n')
}
while ((match = reg.exec(tpl))) {
add(tpl.slice(cursor, match.index))
add(match[1], true)
cursor = match.index + match[1].length
// 重新定位游標(biāo)位置
}
add(tpl.substr(cursor, tpl.length - cursor))
}
第五步:完善 code 函數(shù)體楼吃,并生成一個(gè)函數(shù)
code 除了包含我們分割的代碼,還應(yīng)該有個(gè) return妄讯, return 回的就是我們?cè)跀?shù)組 r 拼接后的字符串
let templateDel = function(id, data, toWhere) {
let tpl = document.getElementById(id).innerHTML
var reg = /<%\s*([^%>]+)?\s*%>/g,
cursor = 0, // 默認(rèn)位置為0
reExp = /(^( )?(if|for|else|switch|case|break|{|}))(.*)?/g, // 該正則是用來檢測是否是for孩锡,if等函數(shù)語句
code = 'var r=[]; \n',
add = function(line, js) {
// js為判斷是否是在模板標(biāo)識(shí)符中包含的代碼塊
js
? (code += line.match(reExp) ? line + '\n' : 'r.push(' + line + ');\n')
: (code += 'r.push("' + line.replace(/"/g, '\\"') + '");\n')
}
while ((match = reg.exec(tpl))) {
add(tpl.slice(cursor, match.index))
add(match[1], true)
cursor = match.index + match[1].length
// 重新定位游標(biāo)位置
}
add(tpl.substr(cursor, tpl.length - cursor))
code += 'return r.join("")'
let fn = new Function('obj', code.replace(/[\r\t\n]/g, ''))
console.log(fn)
document.getElementById(toWhere).innerHTML = fn(data.obj)
}
第六步:檢測代碼
let data = {
obj: {
arr: ['a', 'b', 'c', 'd']
}
}
templateDel('item_tmpl', data, 'results')
// 結(jié)果為:
<div id="results">
<p>這是obj.arr的第0個(gè)a</p>
<a href="">0</a>
<p>這是obj.arr的第1個(gè)b</p>
<a href="">1</a>
<p>這是obj.arr的第2個(gè)c</p>
<p>這是obj.arr的第3個(gè)d</p>
<a href="">3</a>
</div>
第七步: 進(jìn)一步完善
這里定義fn時(shí),傳入的形參為obj亥贸,限制了使用躬窜。有很大的局限性
let templateDel = function(id, data, toWhere) {
let tpl = document.getElementById(id).innerHTML
var reg = /<%\s*([^%>]+)?\s*%>/g,
cursor = 0, // 默認(rèn)位置為0
reExp = /(^( )?(if|for|else|switch|case|break|{|}))(.*)?/g, // 該正則是用來檢測是否是for,if等函數(shù)語句
code = 'var r=[]; \n',
add = function(line, js) {
// js為判斷是否是在模板標(biāo)識(shí)符中包含的代碼塊
js
? (code += line.match(reExp) ? line + '\n' : 'r.push(' + line + ');\n')
: (code += 'r.push("' + line.replace(/"/g, '\\"') + '");\n')
}
while ((match = reg.exec(tpl))) {
add(tpl.slice(cursor, match.index))
add(match[1], true)
cursor = match.index + match[1].length
// 重新定位游標(biāo)位置
}
add(tpl.substr(cursor, tpl.length - cursor))
code += 'return r.join("")'
let fn = new Function(code.replace(/[\r\t\n]/g, '')).call(this, data)
console.log(fn)
document.getElementById(toWhere).innerHTML = fn.apply(data)
}
改動(dòng)一下模板用法
<script type="text/html" id="item_tmpl">
<% for(var i=0; i< this.obj.arr.length; i++){ %>
<p>這是obj.arr的第<%i%>個(gè)<% this.obj.arr[i] %></p>
<% if(i != 2){ %>
<a href=""><%i%></a>
<%}%>
<%}%>
</script>
可能遇到的問題
模板片段分割時(shí)砌函,記得將"
轉(zhuǎn)譯成\"
,不然會(huì)報(bào)錯(cuò)
不要忘了最后一段分割的代碼斩披,即add(tpl.substr(cursor, tpl.length - cursor))