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原生态函数中使用jQuery中的 $(this)无效的解决方法
May 25 Javascript
自己做的模拟模态对话框实现代码
May 23 Javascript
Bootstrap实现基于carousel.js框架的轮播图效果
May 02 Javascript
纯JavaScript实现实时反馈系统时间
Oct 26 Javascript
jQuery图片加载失败替换默认图片方法汇总
Nov 29 jQuery
基于vue cli重构多页面脚手架过程详解
Jan 23 Javascript
Angular6新特性之Angular Material
Dec 28 Javascript
详解babel升级到7.X采坑总结
May 12 Javascript
vue cli3 配置proxy代理无效的解决
Oct 30 Javascript
详解vue高级特性
Jun 09 Javascript
vue项目开启Gzip压缩和性能优化操作
Oct 26 Javascript
vue+spring boot实现校验码功能
May 27 Vue.js
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中删除变量时unset()和null的区别分析
2011/01/27 PHP
php目录遍历函数opendir用法实例
2014/11/20 PHP
PHP通过API获取手机号码归属地
2015/05/28 PHP
PHP在innodb引擎下快速代建全文搜索功能简明教程【基于xunsearch】
2016/10/14 PHP
CI框架常用函数封装实例
2016/11/21 PHP
ThinkPHP下表单令牌错误与解决方法分析
2017/05/20 PHP
js监听表单value的修改同步问题,跨浏览器支持
2009/12/31 Javascript
在jquery boxy中添加百度地图坐标拾取注意流程
2014/04/03 Javascript
jQuery调取jSon数据并展示的方法
2015/01/29 Javascript
基于jQuery实现中英文切换导航条效果
2016/09/18 Javascript
Bootstrap 设置datetimepicker在屏幕上面弹出设置方法
2017/03/21 Javascript
Layui table 组件的使用之初始化加载数据、数据刷新表格、传参数
2017/09/11 Javascript
使用Vue组件实现一个简单弹窗效果
2018/04/23 Javascript
jQuery分组选择器简单用法示例
2019/04/04 jQuery
详解vue 动态加载并注册组件且通过 render动态创建该组件
2019/05/30 Javascript
js实现列表按字母排序
2020/08/11 Javascript
[00:03]DOTA2新版本PA至宝展示
2014/11/19 DOTA
[02:03]永远的信仰DOTA2 中国军团历届国际邀请赛回顾
2016/06/26 DOTA
[01:11:15]VGJ.S vs Secret 2018国际邀请赛小组赛BO2 第一场 8.16
2018/08/17 DOTA
Python自定义函数定义,参数,调用代码解析
2017/12/27 Python
python去掉 unicode 字符串前面的u方法
2018/10/21 Python
python实现简单名片管理系统
2018/11/30 Python
python实现最短路径的实例方法
2020/07/19 Python
python 实现压缩和解压缩的示例
2020/09/22 Python
css3实现顶部社会化分享按钮示例
2014/05/06 HTML / CSS
从零实现一个自定义html5播放器的示例代码
2017/08/01 HTML / CSS
美国林业供应商:Forestry Suppliers
2019/05/01 全球购物
JVM是一个编译程序还是解释程序
2012/09/11 面试题
优秀学生党员先进事迹材料
2014/05/29 职场文书
创先争优个人承诺书
2014/08/30 职场文书
工作作风承诺书
2014/08/30 职场文书
公司领导班子对照检查存在问题整改措施
2014/10/02 职场文书
2014年合同管理工作总结
2014/12/02 职场文书
Python爬虫进阶之Beautiful Soup库详解
2021/04/29 Python
Go语言实现一个简单的并发聊天室的项目实战
2022/03/18 Golang
总结三种用 Python 作为小程序后端的方式
2022/05/02 Python