在小公司待久了感觉自己的知识面很小,最近逛博客园和一些技术网站看大家在说JavaScript模版引擎的事儿,完全没有概念,网上一搜这是08年开始流行起来的。。。本来以为这是很高深的知识,后来在网上看到jQuery作者John Resig,研究了一下,算是明白了最简单的javaScript模版引擎的原理,并没有想象的那么高大上,写篇博客推导一下John Resig写法的过程,写出一个最简单的JavaScript模版引擎。

什么是JavaScript引擎

其实在网站开发中模板还是很常见的一种技术,比如PHP的Smarty、ASP.NET的Master Page等,但这些模板都是基于服务器的,JavaScript模板引擎是为了解决我们在前端写出形如这样的拼html的语句

var html='<ul>';
for(var i=0;i<users.length;i++){
html+='<li><a href=">'+users[i].url+'">'+users[i].name+'</a>';
}
html+='</ul>'; document.getElementById('results').innerHTML=html;

上面的代码我们一看就知道是在拼html,但具体拼的什么很难说清,需要逐句去读代码,如果我们有这样一个模板

<ul>
<% for ( var i = 0; i < users.length; i++ ) { %>
<li><a href="<%=users[i].url%>"><%=users[i].name%></a></li>
<% } %>
</ul>

看了很容易就明白开发者希望得到的是这样的html

<ul>
<li><a href="XXX">OOO</a></li>
<li><a href="XXX">OOO</a></li>
<li><a href="XXX">OOO</a></li>
</ul>

JavaScript模板引擎就是帮我们把带有JavaScript代码的伪html语句翻译为html的东东

John Resig的实现方式

先看看John Resig是怎么实现最简单的一个JavaScript模板引擎的

 // Simple JavaScript Templating
// John Resig - http://ejohn.org/ - MIT Licensed
(function(){
var cache = {}; this.tmpl = function tmpl(str, data){
// Figure out if we're getting a template, or if we need to
// load the template - and be sure to cache the result.
var fn = !/\W/.test(str) ?
cache[str] = cache[str] ||
tmpl(document.getElementById(str).innerHTML) : // Generate a reusable function that will serve as a template
// generator (and which will be cached).
new Function("obj",
"var p=[],print=function(){p.push.apply(p,arguments);};" + // Introduce the data as local variables using with(){}
"with(obj){p.push('" + // Convert the template into pure JavaScript
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('');"); // Provide some basic currying to the user
return data ? fn( data ) : fn;
};
})();

看完上面代码就明白的同学就不用看下面内容了,没太明白的同学可以和我一块儿看看着三十多句代码为什么能够实现一个JavaScript引擎吧。

模板的语法

模板的语法很简单,有三条基本规则

  1. 用正常的方式书写html
  2. 用<% %>嵌套JavaScript语句
  3. 用<%= %>嵌套JavaScript 变量值

模板转换为html字符串原理

我们的JavaScript引擎正式设计为识别这种类型的模板的,拿上面的做例子,这样的一个模版

<ul>
<% for ( var i = 0; i < users.length; i++ ) { %>
<li><a href="<%=users[i].url%>"><%=users[i].name%></a></li>
<% } %>
</ul>

想得到预期html字符串,我们必须设法让模板内部的javascript变量置换、javaScript语句执行,也就是把JavaScript代码剥离出来执行,把其它html语句拼接为一个字符串

var p=[];

p.push('<ul>');
for(var i=0;i<users.length;i++){ //javascript语句执行
p.push('<li><a href="'); //html语句拼接
p.push(users[i].url); //javascript变量置换后拼接
p.push('">');
p.push(users[i].name);
p.push('</a></li>');
}
p.push('</ul>');

最后得到的数组join一下就是我们希望得到的字符串了,首先需要取到模板内的字符串,这个简单按照John的做法我们可以把模板放到一个script标签里(防止在页面显示出来),换成我们特定的类型

    <script type="text/html" id="user_tmpl">
<ul>
<% for ( var i = 0; i < users.length; i++ ) { %>
<li>
<a href="<%=users[i].url%>">
<%=users[i].name%>
</a>
</li>
<% } %>
</ul>
</script>

这样就可以通过 document.getElementById(str).innerHTML 来获取模版内字符串了,然后我们应用一些简单的法则处理一下模板内字符串

<%=xxx%>           =>     ');p.push(xxx);p.push('

<%                 =>     ');

%>                 =>     p.push('

这样我们就可以得到这样的结构,看起来就已经很接近结果了

p.push('<ul>');
for(var i=0;i<users.length;i++){
p.push('<li><a href="');
p.push(users[i].url);
p.push('">');
p.push(users[i].name);
p.push('</a></li>');
}
p.push('</ul>');

简单的字符串置换

现在我们根据上面规则做替换了,这里得使用一些正则表达式和replace函数的知识,不太熟悉的同学可能需要看看  JavaScript 正则表达式上——基本语法  JavaScript正则表达式下——相关方法

1.把<%=xxx%> 替换为 ');p.push(xxx);p.push('

html=html.replace(/<%=(.*?)%>/g,"');p.push(xxx);p.push('");

2.把<%替换为 ');

html=html.replace(/<%/g,"');");

3.把%> 替换为 p.push('

html=html.replace(/%>/g,"p.push('");

我们再把结果用p.push(' 和 '); 包裹起来就可以看到初步效果了

function tmpl(id,data){
var html=document.getElementById(id).innerHTML;
var result="var p=[]; p.push('"
+html.replace(/<%=(.*?)%>/g,"');p.push(xxx);p.push('")
.replace(/<%/g,"');")
.replace(/%>/g,"p.push('")
+" ');";
}

这样我们就把html模版内容替换成了这样的一个字符串

var result="
var p=[];
p.push('<ul>');
for(var i=0;i<users.length;i++){
p.push('<li><a href="');
p.push(users[i].url);
p.push('">');
p.push(users[i].name);
p.push('</a></li>');
}
p.push('</ul>');"

貌似得到结果了,但我们得到的是字符串,我们预期的是这个字符串执行的结果,很多同学会想到使用eval就可以让字符串变成JavaScript语句执行,但是Jonh使用了另外一种方式——创建function,我们知道除了常用使用function关键字创建一个function

function fn(data){
console.log(data);
}

还可以使用Function构造函数来创建一个function

var fn = new Function(arg1, arg2, ..., argN, function_body)

在上面的形式中,每个 arg 都是一个参数,最后一个参数是函数主体(要执行的代码),使用这种方式可以动态(方法体是动态生成的,提前不知道,当然这样做会有效率问题)创建一个方法,也就是说我们还可以使用刚才拼出来的javascript字符串动态创建一个函数

function tmpl(id,data){
var html=document.getElementById(id).innerHTML;
var result="var p=[];p.push('"
+html.replace(/[\r\n\t]/g," ")
.replace(/<%=(.*?)%>/g,"');p.push($1);p.push('")
.replace(/<%/g,"');")
.replace(/%>/g,"p.push('")
+"');return p.join('');";
var fn=new Function(data,result);
return fn(data);
}

这样看起来很科学了,但是我们执行一下会报错,原因很简单就是参数的作用域不对,我们需要改变一下动态构造的方法的作用域,这个有很多方式比如apply函数啊什么的,我们暂且采用John的方式——使用with关键字改变作用域

function tmpl(id,data){
var html=document.getElementById(id).innerHTML;
var result="var p=[];with(obj){p.push('"
+html.replace(/[\r\n\t]/g," ")
.replace(/<%=(.*?)%>/g,"');p.push($1);p.push('")
.replace(/<%/g,"');")
.replace(/%>/g,"p.push('")
+"');}return p.join('');";
var fn=new Function("obj",result);
return fn(data);
}

虽然看起来和John的方法还有很大区别,不过我们已经偷师到了其精髓,实现了一个最简单JavaScript模版引擎,你是不是也明白了JavaScript模版引擎是什么了呢?就是简单的字符串替换,剥离出JavaScript语句,然后利用新的字符串构造函数,返回结果。

看个例子

<!DOCTYPE html>
<html>
<head>
<title>Template</title>
</head>
<body> <div id="results"></div> <script type="text/html" id="user_tmpl">
<ul>
<% for ( var i = 0; i < users.length; i++ ) { %>
<li><a href="<%=users[i].url%>"><%=users[i].name%></a></li>
<% } %>
</ul>
</script> <script type="text/javascript">
var results = document.getElementById("results");
var users=[
{"name":"Byron", "url":"http://localhost"},
{"name":"Casper", "url":"http://localhost"},
{"name":"Frank", "url":"http://localhost"}
]; function tmpl(id,data){
var html=document.getElementById(id).innerHTML;
var result="var p=[];with(obj){p.push('"
+html.replace(/[\r\n\t]/g," ")
.replace(/<%=(.*?)%>/g,"');p.push($1);p.push('")
.replace(/<%/g,"');")
.replace(/%>/g,"p.push('")
+"');}return p.join('');";
var fn=new Function("obj",result);
return fn(data);
} results.innerHTML = tmpl("user_tmpl", users);
</script>
</body>
</html>

应用了简单的JavaScript模版引擎,我们可以很方便的拼出一些html了

参考

John Resig JavaScript Micro-Templating
汤姆大叔 大叔手记(7):构建自己的JavaScript模板小引擎
BarretLee JavaScript模板引擎原理,几行代码的事儿
 
PS.
目前写的JavaScript模版引擎还有很多性能、特殊情况处理问题,下篇博客(简单JavaScript模版引擎优化)会继续进行一些优化工作
 

最简单的JavaScript模板引擎的更多相关文章

  1. 转载---最简单的JavaScript模板引擎

    转载自:http://www.cnblogs.com/dolphinX/p/3489269.html,http://blog.jobbole.com/56689/

  2. 推荐13款javascript模板引擎

    javaScript 在生成各种页面内容时如果能结合一些模板技术,可以让逻辑和数据之间更加清晰,本文介绍 X 款 JavaScript 的模板引擎.(排名不分先后顺序) 1. Mustache 基于j ...

  3. JavaScript模板引擎实例应用

    在之前的一篇名为<移动端基于HTML模板和JSON数据的JavaScript交互>的文章中,我向大家说明了为什么要使用JavaScript模板以及如何使用,文末还提到了laytpl.art ...

  4. JavaScript 模板引擎实现原理解析

    1.入门实例 首先我们来看一个简单模板: <script type="template" id="template"> <h2> < ...

  5. Javascript模板引擎mustache.js详解

    mustache.js是一个简单强大的Javascript模板引擎,使用它可以简化在js代码中的html编写,压缩后只有9KB,非常值得在项目中使用.本文总结它的使用方法和一些使用心得,内容不算很高深 ...

  6. 如何选择Javascript模板引擎(javascript template engine)?

    译者 jjfat 日期:2012-9-17  来源: GBin1.com 随着前端开发的密集度越来越高,Ajax和JSON的使用越来越频繁,大家肯定免不了在前台开发中大量的使用标签,常见到的例子如下: ...

  7. 【JavsScript】推荐五款流行的JavaScript模板引擎

    摘要:Javascript模板引擎作为数据与界面分离工作中最重要一环,受到开发者广泛关注.本文通过开发实例解析五款流行模板引擎:Mustache.Underscore Templates.Embedd ...

  8. 高性能JavaScript模板引擎原理解析

    随着 web 发展,前端应用变得越来越复杂,基于后端的 javascript(Node.js) 也开始崭露头角,此时 javascript 被寄予了更大的期望,与此同时 javascript MVC ...

  9. JavaScript模板引擎

    JavaScript模板引擎实例应用   在之前的一篇名为<移动端基于HTML模板和JSON数据的JavaScript交互>的文章中,我向大家说明了为什么要使用JavaScript模板以及 ...

随机推荐

  1. js操作数组

    一.数组的声明方式: var colors = new Array();//创建数组 var colors = new Array(20);//创建20个长度的数组 var colors = new ...

  2. Thinking in Java 笔记

    大二就买了这本书,如今再看这本书,看到了一些以前没看的细节,也有了不同的体会.本文使用第4版,整理每章的笔记心得.老外的书有个特点,他会花费大量的文字去阐述一个概念,这比堆代码强多了. 第 1 章 对 ...

  3. Symantec Backup Exec 2012 Agent for Linux 卸载

    本文介绍一下如何卸载Symantec Backup Exec 2012 Agent for Linx.首先我们来看看Symantec_Backup_Exec2012管理员手册的文档介绍: 卸载 Age ...

  4. SQL SERVER导出特殊格式的平面文件

    有时候我们需要将SQL SERVER的数据一次性导入到ORACLE中,对于数据量大的表.我一般习惯先从SQL SERVER导出特殊格式的平面文件(CSV或TXT),然后用SQL*Loader装载数据到 ...

  5. EF如何操作内存中的数据以及加载相关联表的数据:延迟加载、贪婪加载、显示加载

    之前的EF Code First系列讲了那么多如何配置实体和数据库表的关系,显然配置只是辅助,使用EF操作数据库才是每天开发中都需要用的,这个系列讲讲如何使用EF操作数据库.老版本的EF主要是通过Ob ...

  6. oracle in VS or效率

    select * from test where status in ('01', '02', '03', '111'); select * from test where status = '01' ...

  7. Spark官方文档 - 中文翻译

    Spark官方文档 - 中文翻译 Spark版本:1.6.0 转载请注明出处:http://www.cnblogs.com/BYRans/ 1 概述(Overview) 2 引入Spark(Linki ...

  8. register_shutdown_function AND fastcgi_finish_request

    在php中又两个方法都是在请求快结束的时候执行.方法名分别是 register_shutdown_function和fastcgi_finish_request.虽然执行的时机差不多,但是功能和应用场 ...

  9. Linux 使用iostat分析IO性能

    原文:http://www.cnblogs.com/bangerlee/articles/2547161.html 对于I/O-bond类型的进程,我们经常用iostat工具查看进程IO请求下发的数量 ...

  10. Junit测试Controller(MockMVC使用),传输@RequestBody数据解决办法

    一.单元测试的目的 简单来说就是在我们增加或者改动一些代码以后对所有逻辑的一个检测,尤其是在我们后期修改后(不论是增加新功能,修改bug),都可以做到重新测试的工作.以减少我们在发布的时候出现更过甚至 ...