在实现一套基本的模板引擎前我先介绍一下可能用到的一些东西
- String.prototype.replace
- 正则匹配与子匹配
- 方法的另一种定义方式
String.prototype.replace
这个字符串的替换方法,本身有几个重载(当然在js中本身没有重载,只是对参数做了一些特殊的处理),今天我只想介绍一下这种:
var str = "{zhzello} {zworldz},{zhelloz} zhangsanz";
var reg = /{([\s\S].)?}/g;
// 其中callback会根据匹配的内容传入不同参数,每一次匹配都会调用
// -无子匹配时接收三个参数:匹配项目、匹配索引、原字符串
// -有子匹配时接收4个参数:匹配项目、子匹配项目、匹配索引、原字符串
// 返回一个新串替换原串中的匹配项目
str.replace(reg,callback);
正则匹配与子匹配
我了解的也不多,具体参考一下另一篇
方法的另一种定义方式
function doSth(){
console.log('I can do something!');
}
var doSth2 = new Function('console.log(\'I can do something!\');');
上面的两种方法的定义效果是一样的,只是平时不推荐使用new Function,但其实通过function定义的方法最后还是会转成new Function的形式(请允许我做一个XX的表情)。
模板引擎的实现
首先看一下代码
* <ul>
* <%for(var i =0;i<list.length;i++){%>
* <li>
* <%= list[i] %>
* </li>
* <%}%>
* </ul>
* <%}%>
*/
let str = 'var tmp = \'\';';
str += 'tmp += \'<br />\';';
str += 'if(list&&list.length){';
str += 'tmp += \'<ul>\'';
str += 'for(var i =0;i<list.length;i++){';
str += 'tmp += \'<li>\'';
str += 'tmp += list[i]';
str += 'tmp += \'</li>\'';
str += '}';
str += 'tmp += \'</ul>\'';
模板的解析主要就是一个动态js调用与字符串的拼接过程,我将之分成下面几步:
1、找到表达式的开始位置;
2、拼接表达式之前的字符串;
3、判断表达式是赋值还是其它表达式,赋值时求值拼接,否则保留表达式;
4、找到下一个表达式的位置;
5、找到表达式与上一次表达式之前的字符串拼接;
。
。
。
N、将最后的字符串加入
N+1、将上面的表达式通过new Function构造成一个函数并调用就可得到编译后的文本
具体参照代码
function buildFunc(tpl){
let index = 0;
let reg = /<%([\s\S]+?)%>/g;
//out 为编译后的方法体
let body = 'var out=\'\';';
body += '';
tpl.replace(reg,(match,val,offset)=>{
body += 'out += \''+ tpl.substring(index,offset) +'\';';
if(match.includes('<%=')){
//如果是取值将输出的字符串加上取值表达式即可
body += 'out += '+val.substr(1)+';';
}else{
//不是取值就将表达式拼到方法体就即可
body += val;
}
index =offset+match.length;//设置代码后面的位置
});
//将最后的字符串加入
body += tpl.substring(index);
body += 'return out;'
return body;
}
var tpl ='';//模板字符串
var func = new Function(buildFunc(tpl));
func()// 输出编译后的文本
在不引用外部变量的情况下,现在已经基本可以成功,但对于我们前面给到的模板
var tpl ='<br />\
<% if(list&&list.length){ %>\
<ul>\
<%for(var i =0;i<list.length;i++){%>\
<li>\
<%= list[i] %>\
</li>\
<%}%>\
</ul>\
<%}%>';
var list= ['Hello','World','你好','世界'];
var func = new Function(buildFunc(tpl));
func()// list is not defined
会报出list is not defined的错误,因为使用Function构造器生成的函数,并不会在创建它们的上下文中创建闭包;它们一般在全局作用域中被创建。当运行这些函数的时候,它们只能访问自己的本地变量和全局变量,不能访问Function构造器被调用生成的上下文的作用域。这和使用带有函数表达式代码的eval不同。
那我们怎么将list弄到动态方法的作用域中去呢?使用with将动态方法的方法体用with块包起来就可以了
var funcBody = buildFunc(tpl);
funcBody = 'with(Data){'+funcBody+'}';
var func = new Function('Data',funcBody);
func({list:list})// 输出正确的
网友评论