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


Posted in Javascript onDecember 14, 2018

本文实例讲述了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"
  }
 ];

ok,现在的问题就是我们怎么把数据导入到模板里面呢?

第一种大家会想到的就是采用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 使用的是<%  %>。

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() 传一个字符串作为函数的body来构造一个 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 函数 在我们的模板里面是没有用到的。

要指出的是,采用 push 的方法在 IE6-8 的浏览器下会比 += 的形式快,但是在现在的浏览器里面, += 是拼接字符串最快的方法。实测表明现代浏览器使用 += 会比数组 push 方法快,而在 v8 引擎中,使用 += 方式比数组拼接快 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 相关文章推荐
用js解决数字不能换行问题
Aug 10 Javascript
浅谈js中变量初始化
Feb 03 Javascript
js实现内容显示并使用json传输数据
Mar 16 Javascript
jquery弹出遮掩层效果【附实例代码】
Apr 28 Javascript
jquery与ajax获取特殊字符实例详解
Jan 08 Javascript
JS ES6多行字符串与连接字符串的表示方法
Apr 26 Javascript
简单实现jQuery轮播效果
Aug 18 jQuery
Vue2.0 实现歌手列表滚动及右侧快速入口功能
Aug 08 Javascript
Element Table的row-class-name无效与动态高亮显示选中行背景色
Nov 30 Javascript
如何测量vue应用运行时的性能
Jun 21 Javascript
小程序如何支持使用 async/await详解
Sep 12 Javascript
JS求解两数之和算法详解
Apr 28 Javascript
Angular2 自定义表单验证器的实现方法
Dec 14 #Javascript
JavaScript模板引擎应用场景及实现原理详解
Dec 14 #Javascript
详解React 服务端渲染方案完美的解决方案
Dec 14 #Javascript
JS/HTML5游戏常用算法之路径搜索算法 A*寻路算法完整实例
Dec 14 #Javascript
JS实现的A*寻路算法详解
Dec 14 #Javascript
详解vue项目接入微信JSSDK的坑
Dec 14 #Javascript
微信小程序实现动态显示和隐藏某个控件功能示例
Dec 14 #Javascript
You might like
PHP 基本语法格式
2009/12/15 PHP
PHP实现的MongoDB数据库操作类分享
2014/05/12 PHP
一个简洁实用的PHP缓存类完整实例
2014/07/26 PHP
php实现的日历程序
2015/06/18 PHP
解决yii2左侧菜单子级无法高亮问题的方法
2016/05/08 PHP
php生成Android客户端扫描可登录的二维码
2016/05/13 PHP
PHP命令行执行整合pathinfo模拟定时任务实例
2016/08/12 PHP
Yii框架安装简明教程
2020/05/15 PHP
『jQuery』.html(),.text()和.val()的概述及使用
2013/04/22 Javascript
3分钟写出来的Jquery版checkbox全选反选功能
2013/10/23 Javascript
javascript:window.open弹出窗口的位置问题
2014/03/18 Javascript
js闭包的用途详解
2014/11/09 Javascript
JS字符串统计操作示例【遍历,截取,输出,计算】
2017/03/27 Javascript
JS简单生成随机数(随机密码)的方法
2017/05/11 Javascript
Ext JS 实现建议词模糊动态搜索功能
2017/05/13 Javascript
vue mint-ui学习笔记之picker的使用
2017/10/11 Javascript
javascript显示动态时间的方法汇总
2018/07/06 Javascript
详解mpvue scroll-view自动回弹bug解决方案
2018/10/01 Javascript
微信小程序实现富文本图片宽度自适应的方法
2019/01/20 Javascript
Python 文件操作技巧(File operation) 实例代码分析
2008/08/11 Python
基于使用paramiko执行远程linux主机命令(详解)
2017/10/16 Python
Python os.rename() 重命名目录和文件的示例
2018/10/25 Python
python Pexpect 实现输密码 scp 拷贝的方法
2019/01/03 Python
浅析python的Lambda表达式
2019/02/27 Python
Python大数据之网络爬虫的post请求、get请求区别实例分析
2019/11/16 Python
BONIA波尼亚新加坡官网:皮革手袋,鞋类和配件
2016/08/25 全球购物
阿联酋彩妆品牌:OUD MILANO
2019/10/06 全球购物
美国相机和电子产品零售商:Beach Camera
2020/11/26 全球购物
电大学习个人自我评价范文
2013/10/04 职场文书
餐饮主管岗位职责
2013/12/10 职场文书
英语专业学生个人求职信范文
2014/01/06 职场文书
经济贸易系毕业生求职信
2014/05/31 职场文书
生日庆典策划方案
2014/06/02 职场文书
安全生产宣传标语
2014/06/06 职场文书
委托书的格式
2014/08/01 职场文书
GoFrame框架数据校验之校验结果Error接口对象
2022/06/21 Golang