JavaScript模板引擎介绍、原理、实现

分类:JavaScript
 标签:JavaScript,模板引擎,artTemplate
   修改 | 阅读(1159)| 评论(0)

什么是JavaScript引擎?JavaScript引擎实现的原理是什么?javascript 模板引擎artTemplate介绍。


随着 web 发展,前端应用变得越来越复杂,此时 javascript 被寄予了更大的期望,与此同时 javascript MVC 思想也开始流行起来。javascript 模板引擎作为数据与界面分离工作中最重要一环,越来越受开发者关注。本文将用最简单的示例代码描述现有的 javascript 模板引擎的原理,包括新一代 javascript 模板引擎 artTemplate 的特性实现原理,欢迎共同探讨。


什么是JavaScript引擎

其实在网站开发中模板还是很常见的一种技术,比如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="url1">name1</a></li>
  <li><a href="url2">name2</a></li>
  <li><a href="rul3">name3</a></li>
</ul>


简单来说,JavaScript模板引擎就是帮我们把带有JavaScript代码的伪html语句翻译为html的东西。


javascript 模板引擎基本原理

虽然每个引擎从模板语法、语法解析、变量赋值、字符串拼接的实现方式各有所不同,但关键的渲染原理仍然是动态执行 javascript 字符串。

关于动态执行 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一下就是我们希望得到的字符串了,首先需要取到模板内的字符串,我们可以把模板放到一个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>');


这里我们要做的只有三步:

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=[];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);
    }


例子

<!DOCTYPE html>
<html>
<head>
    <title>模板</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":"列表1", "url":"http://www.hejingzong.cn"},
        {"name":"列表2", "url":"http://www.hejingzong.cn"},
        {"name":"列表3", "url":"http://www.hejingzong.cn"}
    ];
    //模板函数
    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>

最后会得到如下的结果:



不足之处

在上面的方法中,模板变量赋值采用了 with 语句,字符串拼接采用数组的 push 方法以提升在 IE6、7 下的性能,jQuery 作者 john 开发的微型模板引擎 tmpl 是这种方式的典型代表,参见: http://ejohn.org/blog/javascript-micro-templating/

由原理实现可见,传统 javascript 模板引擎中留下两个待解决的问题:

1、性能:模板引擎渲染的时候依赖 Function 构造器实现,Function 与 eval、setTimeout、setInterval 一样,提供了使用文本访问 javascript 解析引擎的方法,但这样执行 javascript 的性能非常低下。

2、调试:由于是动态执行字符串,若遇到错误调试器无法捕获错误源,导致模板 BUG 调试变得异常痛苦。在没有进行容错的引擎中,局部模板若因为数据异常甚至可以导致整个应用崩溃,随着模板的数目增加,维护成本将剧增。


artTemplate 介绍

artTemplate 是新一代 javascript 模板引擎,它采用预编译方式让性能有了质的飞跃,并且充分利用 javascript 引擎特性,使得其性能无论在前端还是后端都有极其出色的表现。在 chrome 下渲染效率测试中分别是知名引擎 Mustache 与 micro tmpl 的 25 、 32 倍。

速度对比

 

除了性能优势外,调试功能也值得一提。模板调试器可以精确定位到引发渲染错误的模板语句,解决了编写模板过程中无法调试的痛苦,让开发变得高效,也避免了因为单个模板出错导致整个应用崩溃的情况发生。


artTemplate 高效的秘密

1、预编译

在上述模板引擎实现原理中,因为要对模板变量进行赋值,所以每次渲染都需要动态编译 javascript 字符串完成变量赋值。而 artTemplate 的编译赋值过程却是在渲染之前完成的,这种方式称之为“预编译”。artTemplate 模板编译器会根据一些简单的规则提取好所有模板变量,声明在渲染函数头部,这个函数类似:

var render=function($data){
    var content=$data.count,$out='';
    $out += '<h3>';
    if(typeof count === 'string'){
        $out += count;
    }
    $out += '</h3>';
    return $out;
};
render({content:'逍遥乐网博客'});


这个自动生成的函数就如同一个手工编写的 javascript 函数一样,同等的执行次数下无论 CPU 还是内存占用都有显著减少,性能近乎极限。

值得一提的是:artTemplate 很多特性都基于预编译实现,如沙箱规范与自定义语法等。

2、更快的字符串相加方式

很多人误以为数组 push 方法拼接字符串会比 += 快,要知道这仅仅是 IE6-8 的浏览器下。实测表明现代浏览器使用 += 会比数组 push 方法快,而在 v8 引擎中,使用 += 方式比数组拼接快 4.7 倍。所以 artTemplate 根据 javascript 引擎特性采用了两种不同的字符串拼接方式。


下载地址:

https://github.com/aui/artTemplate/

在线预览:

http://aui.github.com/artTemplate/

相关链接:

Juicer —— 一个Javascript模板引擎的实现和优化

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

最简单的JavaScript模板引擎

简单JavaScript模版引擎优化




您的昵称:*
QQ登录(无需注册直接登录可进行回复)
您的邮箱:(填写邮箱,如有回复可进行邮件通知)
验证码:
点击刷新