一个最基本的模板解析器,需要有什么功能呢?
- 读取变量值
- 解析模板语句
按照这个思路,我们编写一个简单的解析器,需求如下:
- 读值: <%= 变量名 %>
- 语句支持: <% if( type == 1 ){ %> qdgithub! <%}%>
html模板如下:
我叫:<%= name %> <br/>
<% if(type == 1){ %>
状态: <%= type %>
<% } %>
如果有数据{name: ‘qdgithub’ type: 1},则需要输出:
我叫:qdgithub
状态:1
按照上面需求,我们需要做什么呢?因为考虑到要在模板内执行脚本,
所以,最直接的办法, 就是把模板翻译为 js
变量
思路:
- 通过正则匹配找出<%…%>的模块,进行处理。
- 把所有匹配出来的内容,拼接成字符串。
- 然后通过 new Function 生成可执行函数。
要想把解析成功,首先需要把数据传入到模板解析器函数中。
// 数据
var data = {name:"qdgithub", type: 1};
// 模板解析器应该有个生成变量声明语句
var statement = '';
for(var name in data){
statement += 'var ' + name + ' = data.' + name + ';';
}
//即:statement = 'var name = data.name;var type = data.type;'
函数
因为模板经常会是某个元素的innerHTML,其中的换行,我们直接替换掉。
<script type="text/html" id="template">
我叫:<%= name %> <br/>
<% if(type == 1){ %>
状态: <%= type %>
<% } %>
</script>
<script>
var html = template.innerHTML;
// 替换掉换行:
html = html.replace(/\s/g, " ");
</script>
考虑到, ‘xx内容<% 表达式 %>’ 这种形式比较好用正则匹配,
所以,把 ‘前面内容<%表达式%>后面内容’ 的 ‘前面’ 和 ‘后面’ 部分去掉:
// resList是最终结果,rightContent是‘后面’部分的内容。
var resList = [],rightContent = '';
html = html.replace(/(.*?)(<%.*%>)(.*?)/g),function(str,left,center,right){
rightContent = right;
resList.push(left);
console.log("left:%s center:%s right:%s", left, center, right);
return center;
});
// 正则匹配的内容将是:
// left: 我叫:
// center: <%= name %> <br/> <% if(type == 1){ %> 状态: <%= type %> <% }%>
// right:
剩下 ‘center’ 的内容,都符合 ‘xx内容<%表达式%>’ 的格式,
所以,使用正则,编译剩下的内容:
var __TMP_LIST__ = '__TMP_LIST__',
__TMP_DATA__ = '__TMP_DATA__';
// 需要编译的动态函数内容
var fnStr = '';
// 开始匹配表达式,匹配 'xxx<% expr %>'
html.replace(/(.*?)<%(.*?)%>/g, function(str, html, exp){
console.log("html:%s exp:%s", html, exp);
// html内容
fnStr += __TMP_LIST__ + ".push('" + html + "');";
// 表达式内容,带等号,取值,不带,则是动态函数的逻辑控制部分
if(exp.indexOf("=") === 0){
fnStr += __TMP_LIST__ + ".push("+ exp.slice(1) +");";
}else{
fnStr += exp;
}
});
生成动态函数
// 构建编译的动态函数
var fn = new Function(__TMP_DATA__, __TMP_LIST__, fnStr);
// 如果结果列表没有数据,则没有需要编译的内容
if(resList.length > 0){
// 把需要编译的数据,结果列表,传入动态函数
fn(data, resList);
// 不要漏了最后一个小尾巴,上面去除 ‘前’ 和 ‘后’部分,留下的
resList.push(rightContent);
console.log(resList.join(""));
}else{
console.log(html);
}