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 相关文章推荐
JavaScript中继承的一些示例方法与属性参考
Aug 07 Javascript
jquery验证手机号码、邮箱格式是否正确示例代码
Jul 28 Javascript
基于jQuery实现的文字按钮表单特效整理
Dec 07 Javascript
js点击文本框后才加载验证码实例代码
Oct 20 Javascript
详解JavaScript编程中正则表达式的使用
Oct 25 Javascript
JS实现的倒计时效果实例(2则实例)
Dec 23 Javascript
Javascript点击按钮随机改变数字与其颜色
Sep 01 Javascript
详解AngularJS 模块化
Jun 14 Javascript
关于页面刷新vuex数据消失问题解决方案
Jul 03 Javascript
vue的diff算法知识点总结
Mar 29 Javascript
vue2中使用sass并配置全局的sass样式变量的方法
Sep 04 Javascript
vue项目中实现的微信分享功能示例
Jan 21 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安全开发 添加随机字符串验证,防止伪造跨站请求
2013/02/14 PHP
Yii框架登录流程分析
2014/12/03 PHP
php实现计数器方法小结
2015/01/05 PHP
php获取当前页面完整URL地址
2015/12/30 PHP
php+Memcached实现简单留言板功能示例
2017/02/15 PHP
Thinkphp5行为使用方法汇总
2017/12/21 PHP
php PDO属性设置与操作方法分析
2018/12/27 PHP
在你的网页中嵌入外部网页的方法
2007/04/02 Javascript
js下用gb2312编码解码实现方法
2009/12/31 Javascript
jQuery 入门级学习笔记及源码
2010/01/22 Javascript
js RuntimeObject() 获取ie里面自定义函数或者属性的集合
2010/11/23 Javascript
在chrome浏览器中,防止input[text]和textarea在聚焦时出现黄色边框的解决方法
2011/05/24 Javascript
jquery连缀语法如何实现
2012/11/29 Javascript
原生javascript实现图片弹窗交互效果
2015/01/12 Javascript
JS简单实现tab切换效果的多窗口显示功能
2016/09/07 Javascript
VUE使用vuex解决模块间传值问题的方法
2017/06/01 Javascript
2种简单的js倒计时方式
2017/10/20 Javascript
如何获取TypeScript的声明文件.d.ts
2018/05/01 Javascript
使用Nuxt.js改造已有项目的方法
2018/08/07 Javascript
vue父子组件的通信方法(实例详解)
2019/11/10 Javascript
vue el-table实现行内编辑功能
2019/12/11 Javascript
Python字符串切片操作知识详解
2016/03/28 Python
pandas apply 函数 实现多进程的示例讲解
2018/04/20 Python
关于ResNeXt网络的pytorch实现
2020/01/14 Python
墨西哥运动服饰和鞋网上商店:Netshoes墨西哥
2016/07/28 全球购物
实现向右循环移位
2014/07/31 面试题
如何估计一张表的大小(假设该表中有1万条数据)
2016/03/27 面试题
《盲人摸象》教学反思
2014/02/16 职场文书
运动会加油口号
2014/06/07 职场文书
企业催款函范本
2015/06/24 职场文书
宝宝满月祝酒词
2015/08/10 职场文书
2016年综治和平安建设宣传月活动总结
2016/04/01 职场文书
SpringBoot整合Mybatis Generator自动生成代码
2021/08/23 Java/Android
2021好看的国漫排行榜前十名 《完美世界》上榜,《元龙》排名第一
2022/03/18 国漫
把77A收信机改造成收音机
2022/04/05 无线电
PostgreSQL数据库去除重复数据和运算符的基本查询操作
2022/04/12 PostgreSQL