在小公司待久了感觉自己的知识面很小,最近逛博客园和一些技术网站看大家在说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. DOM 概况

    DOM(文档对象模型)是针对 HTML 和 XML 文档的一个API(应用程序编程接口).DOM 描绘了一个层次化的节点树,允许开发人员添加.移除和修改页面的某一部分. 层次节点 DOM可以将任何 H ...

  2. 滑动关闭activity

    // 手指上下滑动时的最小速度 private static final int YSPEED_MIN = 1000; // 手指向右滑动时的最小距离 private static final int ...

  3. cocos2d-x游戏开发系列教程-超级玛丽04-AppDelegate

    代码下载链接 http://download.csdn.net/detail/yincheng01/6864893 解压密码:c.itcast.cn 背景 上一篇博文提到在CCApplication: ...

  4. NGUI_slider

    十一.进度条UISlider 1.一般按以下规律使用进度条; 如果某一钟值,他有最大值,需要表达当前的值得占比,则可以使用不可拖动的进度条 如果某一值,他有最大值,需要玩家记性滑动设置,则可以使用可拖 ...

  5. 深入理解Word2Vec

    Word2Vec Tutorial - The Skip-Gram Model,Skip-Gram模型的实现原理:http://mccormickml.com/2016/04/19/word2vec- ...

  6. GenyMotion the virtual device got no ip address 问题解决

    不要再找答案了 升级你的virtual box到最新版本(目前是 5.0.26,已通过) 如果你是windows 10系统 必须关闭hyper-v 在管理员命令行下运行bcdedit /set hyp ...

  7. SpringBoot集成MyBatis的分页插件PageHelper

    俗话说:好

  8. php 中的查找算法 和 排序方法(多字段排序)

    一.查找算法 1.顺序查找(一个一个查,效率低,不用多说) 2.二分查找 /* php 二分查找 在$a数组里查找$x的位置 $a必须是一个以升序排序后的数组 */ function binsearc ...

  9. IDEA Properties中文unicode转码问题

    在IDEA中创建了properties文件,发现默认中文不会自动进行unicode转码.如下 在project settings - File Encoding,在标红的选项上打上勾,确定即可 效果图 ...

  10. “过时”的SpringMVC我们到底在用什么?深入分析DispatchServlet源码

    之前已经分析过了Spring的IOC(<零基础带你看Spring源码--IOC控制反转>)与AOP(<从源码入手,一文带你读懂Spring AOP面向切面编程>)的源码,本次就 ...