要实现的目标
这是<%name%>的第一个<% content %>
data = {
name: 'a',
content: 'template'
}
变成
这是a的第一个template
这是个比较简单的 html 解析代码,用正则即可完成。思路是将所有的<% %>包裹的东西,与 data 对象的 key 匹配,匹配上替换即可。
let templateDel = function(tpl, data) {
var result = tpl.replace(/<%\s*([^%>|\s]+)?\s*%>/g, function(s0, s1) {
return data[s1]
})
console.log(result)
}
检验一下这个模板处理函数是否达到我们预期的效果
html
id="item_tmpl"
标签中包含着我们需要处理的模板语言,处理完后放到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')
审查页面的元素
这是a的第一个template
a
这样似乎满足了我们一开始的需求,但是如果处理的数据比较复杂,这样似乎就不行了
let data = {
name: 'a',
age: 'template',
obj: {
x: 'xxxx'
}
}
templateDel('item_tmpl', data, 'results')
这时模板里的 obj.x 就被替换成 data[obj.x],也就是 undefined 了
而且模板中一般会有一些循环和条件判断的处理,这样写也没有办法解析
这时我们不能简单的用正则匹配替换就可以,而是换一种思路。即,把模板语句 用 function 执行。
所以我们需要做的就是把模板当作字符串 分割开来,然后在对变量和函数来 处理后在拼接起来。
第一步:用正则将代码的分割
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]是所有用了模板符号的代码,那没有用模板的标签或其他代码怎么表示呢?
}
}
第二步:用一个变量 cursor 来标示当前解析到了哪个位置,可以称之为 游标
let templateDel = function(id, data, toWhere) {
let tpl = document.getElementById(id).innerHTML
var reg = /<%\s*([^%>]+)?\s*%>/g,
cursor = 0 // 默认位置为0
while ((match = reg.exec(tpl))) {
console.log(match)
// 这里match[1]是所有用了模板符号的代码
console.log(match.index)
// 当前正则解析到的模板的开始位置
console.log(tpl.slice(cursor, match.index))
// 当前正则解析到的模板之前的没有用模板的标签或其他代码
cursor = match.index + match[1].length
// 重新定位游标位置
}
}
第三步:将分割完的代码用数组存起来
这里不是简单的定义一个变量类型为数组,然后往里 push,而是应该定义一个 字符串, 该字符串应是我们处理模板的函数体。
定义一个 add 函数,专门处理 分割后的代码往字符串里添加
let templateDel = function(id, data, toWhere) {
let tpl = document.getElementById(id).innerHTML
var reg = /<%\s*([^%>]+)?\s*%>/g,
cursor = 0, // 默认位置为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
// 重新定位游标位置
}
add(tpl.substr(cursor, tpl.length - cursor)) // 这里不能忘了最后一个正则匹配后的非模板标识符包含的代码块
}
第四步:完善 add 函数
我们处理分割后的字符串,应该区分对待
不在模板标识符中的 以字符串形式,存储在数组中
在模板标识符中的变量 以变量形式存在数组中
在模板标识符中的函数语句,如 for,if 等 不存在数组中,而是直接拼接在函数体中
let templateDel = function(id, data, toWhere) {
let tpl = document.getElementById(id).innerHTML
var reg = /<%\s*([^%>]+)?\s*%>/g,
cursor = 0, // 默认位置为0
reExp = /(^( )?(if|for|else|switch|case|break|{|}))(.*)?/g, // 该正则是用来检测是否是for,if等函数语句
code = 'var r=[]; \n',
add = function(line, js) {
// js为判断是否是在模板标识符中包含的代码块
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
// 重新定位游标位置
}
add(tpl.substr(cursor, tpl.length - cursor))
}
第五步:完善 code 函数体,并生成一个函数
code 除了包含我们分割的代码,还应该有个 return, return 回的就是我们在数组 r 拼接后的字符串
let templateDel = function(id, data, toWhere) {
let tpl = document.getElementById(id).innerHTML
var reg = /<%\s*([^%>]+)?\s*%>/g,
cursor = 0, // 默认位置为0
reExp = /(^( )?(if|for|else|switch|case|break|{|}))(.*)?/g, // 该正则是用来检测是否是for,if等函数语句
code = 'var r=[]; \n',
add = function(line, js) {
// js为判断是否是在模板标识符中包含的代码块
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
// 重新定位游标位置
}
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')
// 结果为:
第七步: 进一步完善
这里定义fn时,传入的形参为obj,限制了使用。有很大的局限性
let templateDel = function(id, data, toWhere) {
let tpl = document.getElementById(id).innerHTML
var reg = /<%\s*([^%>]+)?\s*%>/g,
cursor = 0, // 默认位置为0
reExp = /(^( )?(if|for|else|switch|case|break|{|}))(.*)?/g, // 该正则是用来检测是否是for,if等函数语句
code = 'var r=[]; \n',
add = function(line, js) {
// js为判断是否是在模板标识符中包含的代码块
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
// 重新定位游标位置
}
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)
}
改动一下模板用法
可能遇到的问题
模板片段分割时,记得将"
转译成\"
,不然会报错
不要忘了最后一段分割的代码,即add(tpl.substr(cursor, tpl.length - cursor))
大神的实现方式