本文介绍了一个由John Resig编写的轻量级JavaScript模板引擎。该引擎支持模板缓存与预编译功能,能显著提升渲染速度。文章详细解析了其基于Function构造器的实现原理,并探讨了利用text/html类型脚本隐藏模板代码的技巧,以及通过字符串静态替换提升执行效率的性能优化策略。
轻量级JavaScript模板引擎介绍
我在开发JavaScript应用时,一直依赖一个小型工具,它被证明极具实用价值。这是一个极简的模板函数,具备执行速度快、支持缓存机制以及易于上手的特点。接下来,我将分享在使用该工具过程中积累的一些实用技巧。
以下是该模板函数的具体代码实现(更精简的版本可参考即将出版的《Secrets of the JavaScript Ninja》一书):
模板引擎核心代码实现
// 简单JavaScript模板引擎
// John Resig - http://ejohn.org/ - MIT Licensed
(function(){
var cache = {};
this.tmpl = function tmpl(str, data){
// 判断我们是否已经有这么一个模板,或者我们需要载入模板
// 并保证把结果保存到缓存中。
var fn = !/\W/.test(str) ?
cache[str] = cache[str] ||
tmpl(document.getElementById(str).innerHTML) :
// 生成一个可重用的函数,用于提供模板生成功能
// (它会被记录到缓存内).
new Function("obj",
"var p=[],print=function(){p.push.apply(p,arguments);};" +
// 通过with(){}把数据作为本地变量引入
"with(obj){p.push('" +
// 把模板转换未纯javascript代码
str
.replace(/[\r\t\n]/g, " ")
.split("<%").join("\t")
.replace(/((^|%>)[^\t]*)'/g, "$1\r")
.replace(/\t=(.*?)%>/g, "',$1,'")
.split("\t").join("');")
.split("%>").join("p.push('")
.split("\r").join("\\'")
+ "');}return p.join('');");
// 给用户提供一些基本的柯里化功能
return data ? fn( data ) : fn;
};
})();
模板语法与内嵌脚本技巧
你的模板代码结构大致如下(这并非强制格式,但我个人更倾向于这种写法):
<script type="text/html" id="item_tmpl">
<div id="<%=id%>" class="<%=(i % 2 == 1 ? " even" : "")%>">
<div class="grid_1 alpha right">
<img class="righted" src="<%=profile_image_url%>"/>
</div>
<div class="grid_6 omega contents">
<p><b><a href="/<%=from_user%>"><%=from_user%></a>:</b> <%=text%></p>
</div>
</div>
</script>
此外,你也可以选择将脚本直接内嵌到页面中:
<script type="text/html" id="user_tmpl">
<% for ( var i = 0; i < users.length; i++ ) { %>
<li><a href="<%=users[i].url%>"><%=users[i].name%></a></li>
<% } %>
</script>
技巧提示:将脚本嵌入页面时,若将其content-type设置为未知类型(例如本例中的text/html),浏览器将无法识别并忽略执行该脚本,同时搜索引擎爬虫和屏幕阅读器也会忽略它。这是一种非常有效的代码伪装手段,可将模板逻辑安全地嵌入页面。我偏爱这种快速且随性的技术方案,仅需一两个小型模板,即可构建出轻量且高效的Web应用。
模板调用与预编译优化
在脚本中,你可以按如下方式使用该模板:
var results = document.getElementById("results");
results.innerHTML = tmpl("item_tmpl", dataObject);
你可以预先编译模板结果以便后续复用。如果调用模板函数时仅传入一个ID参数(或模板字符串),它将返回一个预编译好的函数,供后续直接调用:
var show_user = tmpl("item_tmpl"), html = "";
for ( var i = 0; i < users.length; i++ ) {
html += show_user( users[i] );
}
性能优化与字符串替换策略
这是目前最无奈的做法,涉及代码的解析与转换——你可能并不喜欢这种实现。但它包含了我偏爱的一项技术:在字符串中进行静态搜索和替换时,使用split("match").join("replace")的执行效率更高。虽然这看起来不够直观,但在现代浏览器中确实有效(注:下一版Firefox将大幅提升replace(/match/g,"replace")的性能,因此未来这种长表达式可能不再必要)。
请放松享受它——我对代码中的突变感到好奇。尽管它很简单,但仍有许多事情可以用它来做。