前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >模板引擎随谈

模板引擎随谈

作者头像
四火
发布2022-07-18 13:58:52
1.9K0
发布2022-07-18 13:58:52
举报
文章被收录于专栏:四火的唠叨

模板引擎是为了解耦而产生的,从编程范型的角度来说,写模板属于 “声明式(Imperative)编程”。JSP 大概是最早接触也是最基础的模板引擎,本来写 Servlet 嘛,一大堆一大堆的 print,实在是没有任何结构性可言,然后 JSP 出现,先被处理成实质为 Servlet 的 Java 文件,编译以后变成 class,接着一样执行。所以本质是编译型的模板引擎,当然模板引擎也有解释型或者二者混合的。通常说来编译型的执行效率要高得多。只要是和显示相关的编程语言,都会发展出一套或者 N 套模板引擎,用得多了觉得很多情况下都大同小异。

几年前我在工作中折腾过一段时间的服务端模板引擎,最早遗留系统使用的 Velocity,后来我们实现的时候用了 FreeMarker,因为后者功能更强大,IDE 支持也更好,对于后者的 macro(宏),实在是不知怎么讲,功能上它当然是一个强大的武器,但是没控制好就会让代码写得功能不清,或者干脆很难看懂。在搞性能调优的时候,到后来不动大刀已经没有什么可以值得改进的地方了。遂眼光瞄到了 FreeMarker 上面,我们拿 profiler 的工具检查出来模板引擎的解释执行耗费了大量的时间,而且其中的模板缓存命中率很低,公司里面有一个团队为此专门改了 FreeMarker 的代码,性能好像有 20% 的提高。

很多人搞 web 开始阶段都是自由生长的,或者说野蛮生长,完全没有章法,凭借着搜索引擎加试错大法,因此方法往往都不正统。我也一样。在我知道专门的模板以前,我已经在粗暴地实现类似的事情了,让一个 DIV 不可见(display=none),然后里面变化的地方用占位符标识,在 Ajax 获得数据以后把占位符替换成真正的文字,然后显示出来——这不就是一最土鳖的模板么?后来开始接触到一些前端模板引擎,Mustache 是最早接触的,我不知道 {{ }} 这样的记号是不是从它开始的,然后是 Handlebars,其实它用的也是 Mustache 的引擎。Underscore.js 是值得推荐的模板引擎,性能非常出色,而且语法和 JSP 差不多。AngularJS 的模板是我最喜欢的形式(下面我列出了一个官网上面的例子),因为直接融合进 HTML 里面了,减少了生硬的特殊格式标签,可以给既有 DOM 对象增加属性,也可以通过 directive 方式自定义 DOM。模板引擎怎么演进而来的,又是怎么从后端移到前端来的,其实都因一个 “解耦”,这个过程我在 《MVC 框架的映射和解耦》以及 《Web 页面的聚合技术》里面都有部分介绍。

代码语言:javascript
复制
<ul>
  <li ng-repeat="phone in phones">
    {{phone.name}}
    <p>{{phone.snippet}}</p>
  </li>
</ul>

关于常见几款前端模板的比较,这里有一篇文章。HTML5 用新标签的方式收录了模板,这里有一篇文章介绍。另外,这里有一个有趣的帖子,作者在入门 Node.js 的时候选模板,很多人在讨论 Jade,它最有意思的地方是如果打开普通的没有代码辅助的记事本文件,它的编写效率真得高出好多,而且没有烦人的括号、尖括号之类的标记符号,不知道你怎么看。对于性能的横向比较,在 JSPerf 上面有人做了一个完整的列表,可以打开页面后立即测试

关于模板引擎的原理解析,推荐一篇文章 《高性能 JavaScript 模板引擎原理解析》,里面提到了 “高性能” 模板引擎的原理,这也是现在越来越多的 JavaScript 模板引擎的设计思路,尽量把工作放到预编译阶段去,生成函数以后,原始的模板就不再使用了,后面每次需要渲染的时候调用这个函数传入参数就可以了。

通过一个小小的例子,可以看到模板引擎的工作原理,这里拿 Handlerbars 举例:

代码语言:javascript
复制
<table>
    {{#each users}}
    <tr>
        <td>
            {{this.name}}
        </td>
        <td>
            {{this.age}}
        </td>
    </tr>
    {{/each}}
</table>

对于这样一段简单的模板,调用语句是:

代码语言:javascript
复制
var func = Handlebars.compile(document.getElementById("template").innerHTML);
var result = func({
	users : [
		{
			name : "A",
			age : 10
		},
		{
			name : "B",
			age : 20
		}
	]
});
console.log(result);

接着动态生成了这样的 Function:

代码语言:javascript
复制
this.compilerInfo = [4,'>= 1.0.0'];
helpers = this.merge(helpers, Handlebars.helpers); data = data || {};
  var buffer = "", stack1, functionType="function", escapeExpression=this.escapeExpression, self=this;

function program1(depth0,data) {
  
  var buffer = "", stack1;
  buffer += "\n	<tr>\n		<td>"
    + escapeExpression(((stack1 = depth0.name),typeof stack1 === functionType ? stack1.apply(depth0) : stack1))
    + "</td>\n		<td>"
    + escapeExpression(((stack1 = depth0.age),typeof stack1 === functionType ? stack1.apply(depth0) : stack1))
    + "</td>\n	</tr>\n	";
  return buffer;
  }

  buffer += "\n<table>\n	";
  stack1 = helpers.each.call(depth0, depth0.users, {hash:{},inverse:self.noop,fn:self.program(1, program1, data),data:data});
  if(stack1 || stack1 === 0) { buffer += stack1; }
  buffer += "\n</table>\n";
  return buffer;

其实代码并不难理解,这里的 each 就是通过内置的工具方法 helpers.each 来实现的,执行总的来说就是递归调用(第 9、11 行),如果 stack1 还是方法就继续调用,否则就直接转码(escapeExpression)显示。最终拼接成字符串输出。

文章未经特殊标明皆为本人原创,未经许可不得用于任何商业用途,转载请保持完整性并注明来源链接 《四火的唠叨》

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档