您的当前位置:首页正文

模板引擎的简单实现

来源:花图问答

在实现一套基本的模板引擎前我先介绍一下可能用到的一些东西

  • 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
var funcBody = buildFunc(tpl);
funcBody = 'with(Data){'+funcBody+'}';
var func = new Function('Data',funcBody);
func({list:list})// 输出正确的