JavaScript模板引擎实现原理实例详解

作者:半夏

时间:2026-10-22

浏览:419

本文深入解析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程序设计有所帮助。

实用工具推荐

电子包浆

绿化、压缩、噪点和虚拟水印,自由调节做旧程度。

使用 ›
图片压缩

选择画质和输出格式,预览压缩前后的大小。

使用 ›
相关文章