JavaScript模板引擎实现原理实例详解
本文深入解析JavaScript模板引擎的实现原理,从基础的变量替换讲到正则表达式的高级应用。重点剖析jQuery tmpl源码,探讨模板解析、编译及字符串拼接的性能优化策略,帮助开发者理解不同模板引擎的核心差异与选型依据。
JavaScript模板引擎概述
本文详细阐述了JavaScript模板引擎的工作机制,旨在为读者提供清晰的实现思路与参考案例。
模板引擎入门实例
1、入门实例
首先观察一个基础的模板结构示例:
<script type="template" id="template">
<h2>
<a href="{{href}}" rel="external nofollow" >
{{title}}
</a>
</h2>
<img src="{{imgSrc}}" alt="{{title}}">
</script>
其中被{{ xxx }}标记包裹的部分即为需要动态替换的变量占位符。
随后,我们可以通过Ajax请求或其他数据获取方式获得所需数据。在此示例中,我们手动定义了数据结构,具体代码如下:
var data = [
{
title: "Create a Sticky Note Effect in 5 Easy Steps with CSS3 and HTML5",
href: "http://net.tutsplus.com/tutorials/html-css-techniques/create-a-sticky-note-effect-in-5-easy-steps-with-css3-and-html5/",
imgSrc: "https://d2o0t5hpnwv4c1.cloudfront.net/771_sticky/sticky_notes.jpg"
},
{
title: "Nettuts+ Quiz #8",
href: "http://net.tutsplus.com/articles/quizzes/nettuts-quiz-8-abbreviations-darth-sidious-edition/",
imgSrc: "https://d2o0t5hpnwv4c1.cloudfront.net/989_quiz2jquerybasics/quiz.jpg"
}
];
明确数据后,核心问题转化为如何将数据注入到模板中。
第一种直观的方法是直接使用replace方法进行字符串替换:
template = document.querySelector('#template').innerHTML,
result = document.querySelector('.result'),
i = 0, len = data.length,
fragment = '';
for ( ; i < len; i++ ) {
fragment += template
.replace( /\{\{title\}\}/, data[i].title )
.replace( /\{\{href\}\}/, data[i].href )
.replace( /\{\{imgSrc\}\}/, data[i].imgSrc );
}
result.innerHTML = fragment;
第二种方法相对灵活,采用正则表达式进行替换。对于初级前端开发者而言,正则表达式掌握难度较大,因此使用频率较低。具体实现如下:
template = document.querySelector('#template').innerHTML,
result = document.querySelector('.result'),
attachTemplateToData;
// 将模板和数据作为参数,通过数据里所有的项将值替换到模板的标签上(注意不是遍历模板标签,因为标签可能不在数据里存在)。
attachTemplateToData = function(template, data) {
var i = 0,
len = data.length,
fragment = '';
// 遍历数据集合里的每一个项,做相应的替换
function replace(obj) {
var t, key, reg;
//遍历该数据项下所有的属性,将该属性作为key值来查找标签,然后替换
for (key in obj) {
reg = new RegExp('{{' + key + '}}', 'ig');
t = (t || template).replace(reg, obj[key]);
}
return t;
}
for (; i < len; i++) {
fragment += replace(data[i]);
}
return fragment;
};
result.innerHTML = attachTemplateToData(template, data);
相比第一种方法,第二种代码量虽多,但功能显著增强。第一种方法需为每个变量名单独编写替换逻辑,变量增多时易出错且繁琐;第二种方法则无需逐一处理变量名,避免了此类烦恼。
模板引擎核心知识
2、模板引擎相关知识
通过上述示例,我们对模板引擎有了初步认知,接下来讲解相关关键技术点。
2.1 模板存放
模板通常存储在textarea、input等表单控件,或script标签中。如前例所示,我们将模板放置于script标签内。
2.2 模板获取
通常通过元素ID获取模板内容,参考document.getElementById("ID"):
//textarea或input则取value,其它情况取innerHTML
var html = /^(textarea|input)$/i.test(element.nodeName) ? element.value : element.innerHTML;
上述方法是通用的模板获取方式,无论模板位于textarea、input还是script标签中均可适用。
2.3 模板函数
通常调用形式为templateFun("id", data);,其中id参数指代存储模板字符串的元素ID,data参数指代需要注入的数据对象。
2.4 模板解析编译
模板解析的核心在于将模板中的JavaScript逻辑语句与HTML静态内容分离;编译则是将模板字符串转化为可执行的最终模板。前述示例较为简单,未涉及模板引擎的核心机制。
2.5 模板分隔符
需注意,不同模板引擎使用的分隔符各异。前述示例使用,而jQuery tmpl则采用<% %>作为分隔符。
jQuery tmpl源码深度解析
3、jQuery tmpl 实现原理解析
jQuery tmpl由jQuery作者编写,代码精简,仅20余行却功能强大。我们先查看其源码:
(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(obj){p.push('" +
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;
};
})();
初看源码可能令人困惑,难以理解。后续我们将对源码进行逐步解析,并先观察其使用的模板格式:
<ul>
<% for ( var i = 0; i < users.length; i++ ) { %>
<li><a href="<%=users[i].url%>" rel="external nofollow" ><%=users[i].name%></a></li>
<% } %>
</ul>
可见,该模板比入门示例更复杂,其中夹杂着JavaScript代码。JavaScript代码由<% %>包裹,而需替换的变量则由<%= %>分隔。
接下来对代码添加注释。即便阅读注释,理解仍可能困难,最佳方式是动手实际操作一遍。
// 代码整个放在一个立即执行函数里面
(function(){
// 用来缓存,有时候一个模板要用多次,这时候,我们直接用缓存就会很方便
var cache = {};
// tmpl绑定在this上,这里的this值得是window
this.tmpl = function tmpl(str, data){
// 只有模板才有非字母数字字符,用来判断传入的是模板id还是模板字符串,
// 如果是id的话,判断是否有缓存,没有缓存的话调用tmpl;
// 如果是模板的话,就调用new Function()解析编译
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(obj){p.push('" +
str
// 去除换行制表符\t\n\r
.replace(/[\r\t\n]/g, " ")
// 将左分隔符变成 \t
.split("<%").join("\t")
// 去掉模板中单引号的干扰
.replace(/((^|%>)[^\t]*)'/g, "$1\r")
// 为 html 中的变量变成 ",xxx," 的形式, 如:\t=users[i].url%> 变成 ',users[i].url,'
// 注意这里只有一个单引号,还不配对
.replace(/\t=(.*?)%>/g, "',$1,'")
// 这时候,只有JavaScript 语句前面才有 "\t", 将 \t 变成 ');
// 这样就可把 html 标签添加到数组p中,而javascript 语句 不需要 push 到里面。
.split("\t").join("');")
// 这时候,只有JavaScript 语句后面才有 "%>", 将 %> 变成 p.push('
// 上一步我们再 html 标签后加了 ');, 所以要把 p.push(' 语句放在 html 标签放在前面,这样就可以变成 JavaScript 语句
.split("%>").join("p.push('")
// 将上面可能出现的干扰的单引号进行转义
.split("\r").join("\\'")
// 将数组 p 变成字符串。
+ "');}return p.join('');");
return data ? fn( data ) : fn;
};
})();
代码中需特别关注new Function 的用法。通过给new Function()传入字符串作为函数体,可以构造一个JavaScript函数。这在编程中不常用,但在特定场景下非常有用。
以下是new Function的基本用法示例:
// 最后一个参数是函数的 body(函数体),类型为 string;
// 前面的参数都是 索要构造的函数的参数(名字)
var myFunction = new Function('users', 'salary', 'return users * salary');
最终生成的字符串形式如下:
var p = [],
print = function() {
p.push.apply(p, arguments);
};
with(obj) {
p.push(' <ul> ');
for (var i = 0; i < users.length; i++) {
p.push(' <li><a href="', users[i].url, '" rel="external nofollow" >', users[i].name, '</a></li> ');
}
p.push(' </ul> ');
}
return p.join('');
其中的print函数在模板中并未实际使用。
值得注意的是,在IE6-8浏览器中,使用push数组拼接字符串的速度优于+=直接拼接;但在现代浏览器中,+=是拼接字符串最快的方式。实测表明,在现代浏览器及v8引擎中,+=方式比数组push方法快4.7倍。因此,部分高级模板引擎会根据JavaScript引擎的特性,采用两种不同的字符串拼接策略。
以下代码摘自腾讯的artTemplate,根据浏览器类型选择拼接方式。功能越强大,需考虑的兼容性问题也越多。
var isNewEngine = ''.trim;// '__proto__' in {}
var replaces = isNewEngine
? ["$out='';", "$out+=", ";", "$out"]
: ["$out=[];", "$out.push(", ");", "$out.join('')"];
挑战:有兴趣的读者可以尝试改用+=实现上述代码逻辑。
模板引擎原理总结
总结
模板引擎的原理可概括为:首先获取HTML中指定ID元素的innerHTML,利用开始和结束标签进行字符串切分,将模板划分为HTML部分和逻辑部分。通过识别特殊符号(如each、if等),将字符串拼接成函数式字符串,分别处理后再次拼接,最后通过new Function()将拼接好的字符串转化为所需函数。
目前模板引擎种类繁多,功能日益强大,各引擎实现原理大同小异,各有优劣,请根据实际需求选择。
参考文章:
1、Quick Tip: Create a Makeshift JavaScript Templating Solution
2、JavaScript模板引擎的应用场景及实现原理
3、JavaScript构建自己的模板小引擎
更多关于JavaScript相关内容可查看本站专题:《javascript面向对象入门教程》、《JavaScript切换特效与技巧总结》、《JavaScript查找算法技巧总结》、《JavaScript错误与调试技巧总结》、《JavaScript数据结构与算法技巧总结》、《JavaScript遍历算法与技巧总结》及《JavaScript数学运算用法总结》
希望本文所述对大家JavaScript程序设计有所帮助。
JavaScript闭包与类(Class)的区别及适用场景对比
2026-11-05 15:48:57
JavaScript入门要学ES6吗?let与箭头函数等新特性解析
2026-11-05 14:49:16
JavaScript 8种数据类型详解:基本类型与引用类型的区别
2026-11-05 14:25:15
怎么在JavaScript中使用async/await优化Ajax异步流程
2026-11-05 13:56:15
JavaScript replace回调函数实现动态正则替换逻辑
2026-11-05 13:52:13
