视频1 视频21 视频41 视频61 视频文章1 视频文章21 视频文章41 视频文章61 推荐1 推荐3 推荐5 推荐7 推荐9 推荐11 推荐13 推荐15 推荐17 推荐19 推荐21 推荐23 推荐25 推荐27 推荐29 推荐31 推荐33 推荐35 推荐37 推荐39 推荐41 推荐43 推荐45 推荐47 推荐49 关键词1 关键词101 关键词201 关键词301 关键词401 关键词501 关键词601 关键词701 关键词801 关键词901 关键词1001 关键词1101 关键词1201 关键词1301 关键词1401 关键词1501 关键词1601 关键词1701 关键词1801 关键词1901 视频扩展1 视频扩展6 视频扩展11 视频扩展16 文章1 文章201 文章401 文章601 文章801 文章1001 资讯1 资讯501 资讯1001 资讯1501 标签1 标签501 标签1001 关键词1 关键词501 关键词1001 关键词1501 专题2001
JavaScript模版引擎的基本实现方法浅析_基础知识
2020-11-27 20:50:46 责编:小采
文档


模板分离了数据与展现,使得展现的逻辑和效果更易维护。利用javascript的Function对象,一步步构建一个极其简单的模板转化引擎

模板简介
模板通常是指嵌入了某种动态编程语言代码的文本,数据和模板通过某种形式的结合,可以变化出不同的结果。模板通常用来定义显示的形式,能够使得数据展现更为丰富,而且容易维护。例如,下面是一个模板的例子:



如果有如下items数据:



通过某种方式的结合,可以产生下面的Html代码:


 
  • text1
  • text2
  • text3
  • text4
  • 如果不使用模板,想要达到同样的效果,即将上面的数据展现成结果的样子,需要像下面这样做:

    
    
    

    可以看出使用模板有如下好处:

    简化了html的书写
    通过编程元素(比如循环和条件分支),对数据的展现更具有控制的能力
    分离了数据与展现,使得展现的逻辑和效果更易维护
    模板引擎
    通过分析模板,将数据和模板结合在一起输出最后的结果的程序称为模板引擎,模板有很多种,相对应的模板引擎也有很多种。一种比较古老的模板称为ERB,在很多的web框架中被采用,比如:ASP.NET 、 Rails … 上面的例子就是ERB的例子。在ERB中两个核心的概念:evaluate和interpolate。表面上evaluate是指包含在<% %>中的部分,interpolate是指包含在<%= %>中的部分。从模板引擎的角度,evaluate中的部分不会直接输出到结果中,一般用于过程控制;而interpolate中的部分将直接输出到结果中。

    从模板引擎的实现上看,需要依赖编程语言的动态编译或者动态解释的特性,以简化实现和提高性能。例如:ASP.NET利用.NET的动态编译,将模板编译成动态的类,并利用反射动态执行类中的代码。这种实现实际上是比较复杂的,因为C#是一门静态的编程语言,但是使用javascript可以利用Function,以极少的代码实现一个简易的模板引擎。本文就来实现一个简易的ERB模板引擎,以展现javascript的强大之处。

    模板文本转化
    针对上面的例子,回顾一下使用模板和不使用模板的差别:

    模板写法:

    
    
    

    非模板写法:

    
    
    

    仔细观察,实际上这两种方法十分“相似”,能够找到某种意义上的一一对应。如果能够将模板的文本变成代码执行,那么就能实现模板转化。在转化过程中有两个原则:

    遇到普通的文本直接当成字符串拼接
    遇到interpolate(即<%= %>),将其中的内容当成变量拼接在字符串中
    遇到evaluate(即<% %>),直接当成代码
    将上面的例子按照上述原则进行变换,再添加一个总的函数:

    
    
    

    最后执行这个函数,传入数据参数即可:

    
    
    

    javascript动态函数
    可见上面的转化逻辑其实十分简单,但是关键的问题是,模板是变化的,这意味着生成的程序代码也必须是在运行时生成并执行的。好在javascript有许多动态特性,其中一个强大的特性就是Function。 我们通常使用function关键字在js中声明函数,很少用Function。在js中function是字面语法,js的运行时会将字面的function转化成Function对象,所以实际上Function提供了更为底层和灵活的机制。

    用 Function 类直接创建函数的语法如下:

    
    
    

    例如:

    
    
    

    函数体和参数都能够通过字符串来创建!So cool!有了这个特性,可以将模板文本转化成函数体的字符串,这样就可以创建动态的函数来动态的调用了。

    实现思路
    首先利用正则式来描述interpolate和evaluate,括号用来分组捕获:

    
    
    

    为了对整个模板进行连续的匹配将这两个正则式合并在一起,但是注意,所有能够匹配interpolate的字符串都能匹配evaluate,所以interpolate需要有较高的优先级:

    
    
    

    设计一个函数用于转化模板,输入参数为模板文本字串和数据对象

    
    
    

    使用replace方法,进行正则的匹配和“替换”,实际上我们的目的不是要替换interpolate或evaluate,而是在匹配的过程中构建出“方法体”:

    
    
    

    至此,function_body虽然是个字符串,但里面的内容实际上是一段函数代码,可以用这个变量来动态创建一个函数对象,并通过data参数调用:

    
    
    

    这样render就是一个方法,可以调用,方法内部的代码由模板的内容构造,但是大致的框架应该是这样的:

    
    
    

    注意到,方法的形参是obj,所以模板内部引用的变量应该是obj:

    
    
    

    看似到这里就OK了,但是有个必须解决的问题。模板文本中可能包含\r \n \u2028 \u2029等字符,这些字符如果出现在代码中,会出错,比如下面的代码是错误的:

    
    
    

    我们希望看到的应该是这样的代码:

    
    
    

    这样需要把\n前面的转义成\即可,最终变成字面的\\n。

    另外,还有一个问题是,上面的代码无法将最后一个evaluate或者interpolate后面的部分拼接进来,解决这个问题的办法也很简单,只需要在正则式中添加一个行尾的匹配即可:

    
    
    

    相对完整的代码

    
    
    

    调用代码可以是这样:

    
    
    

    可见,我们只用了很少的代码就实现了一个简易的模板。

    遗留的问题
    还有几个细节的问题需要注意:

  • 因为<%或者%>都是模板的边界字符,如果模板需要输出<%或者%>,那么需要设计转义的办法
  • 如果数据对象中包含有null,显然不希望最后输出'null',所以需要在function_body的代码中考虑null的情况
  • 在模板中每次使用obj的形参引用数据,可能不太方便,可以在function_body添加with(obj||{}){...},这样模板中可以直接使用obj的属性
  • 可以设计将render返回出去,而不是返回转化的结果,这样外部可以缓存生成的函数,以提高性能
  • 下载本文
    显示全文
    专题