黑铁时代
Programing is not only one kind of technology, but also one kind of art.

在前端技术并不流行的时候,大部分网站都采用PHP端的模版引擎,其中的Smarty是最经典的一种,其实我也只用过Smarty模版引擎。个人认为,不管是什么样的模版引擎,基本思想都应该是类似的。

互联网发展到今天,随着浏览器性能大幅提高,功能日益强大,浏览器能够承载更多的职责,大部分功能都开始由后端慢慢移向前端。将更多的事情交给前端来完成,这样做的好处就是可以减少服务器的压力,也提高了用户体验。将模版引擎的开发引入到前端,就是一个不错的改变。现在有很多优秀的模版引擎库,如backone.js,jQuery的模版引擎。不过不同的模版引擎,他们的原理应该是类似的:

  1. 写一个模版文件,这个文件可以是.tpl格式的,这样可以和smarty共享,如果是独立的tpl文件,那么就需要用请求的方式去服务器取出这个文件的内容,用字符串来存储;也可以用脚本的方式来书写模版文件,如下:

<script type="text/template" id="table-tpl">
  <table border="1" bordercolor="blue">
  <% for( var i = 0, len = $trs.length; i < len; i++ ) { %>
  <% var tr = $trs[i]; %>
    <tr>
      <td><% = tr.name %></td>
      <td><%= tr.age %></td>
      <td><% = tr.sex %></td>
    </tr>
  <% } %>
  </table>
  <a href="<%= $href %>">baidu</a>
</script>

  这里type是text/template,当浏览器看到这个type,它并不识别,所以浏览器通常会忽略,而不去解析它。

  2. 然后就是去取出这个模版文件的内容了,并用字符串保存内容:var tpl = document.getElementById("table-tpl").text;。

  3. 模版文件的书写肯定要有一定规则,就像Smarty一样,也必须按照相应的要求来写,比如:<%和%>中的内容就表示可以进行编程控制的,$后面的内容表示需要被变量替换的,=后面的内容就表示会呈现在界面上的内容的。当然这个规则算是很简单了,但是竟然是模版引擎,主要职责就是呈现内容,而不应该具备复杂的逻辑处理能力。这些复杂的逻辑应该在模版文件外部来实现,模版文件只专注于实现。根据这3个简单的规则,于是实现了自己的模版引擎,代码如下:

/*
* Template engine
* Date 2012.10.10
*/

( function( global ) {
// The border to embed the js code
var leftBorder = "<%", rightBorder = "%>";

// The regular expression
var reg_trim_1 = /^\s*|\s*$/img, // Match the blank of begining and ending
reg_trim_2 = />(\s*)</img, // Match the blank between the two tags
reg_vars = /\$(\w+)((?:(?:\.(?:\w+))|(?:\[\s*(?:[^$]+)\s*\]))*)/ig, // Match the varibles which will be replaced
reg_logic = /<%\s*([^%]+)\s*%>/ig, // Match the js code
reg_output = /<%\s*=\s*([^%]+)%>/ig, // Match the varible will be output in view
reg_tag_beg = /^([^%;]*)(<%)/i, // Match all the tags before the first left border <%
reg_tag = /(%>)([^%;]*)(<%)/ig, // Match all the tags between the first right border %> and the last left border <%
reg_tag_end = /(%>)([^%;]*)$/i; // Match all the tags behind the last right borfer %>

// Some debug varibles
var debug = false,
reg_vars_debug = /\$(\w+)((?:(?:\.(?:\w+))|(?:\[\s*(?:[$\w]+)\s*\]))*)/ig,
reg_sub_prop = /\[\s*([$\w]+)\s*\]|\.(\w+)/ig;

/**
* Clear the blank in the tpl string
* @param str
*/
var trim = function( str ) {
return str.replace( reg_trim_1, '' ).replace( reg_trim_2, '><' );
};

/**
* Check if the varibles will be replaced is defined
* @param value The matched string
* @param vars The object which contain the varibles
*/
var check = function( value, vars ) {
var reg_vars_check = /\$(\w+)((?:(?:\.(?:\w+))|(?:\[\s*(?:[$\w]+)\s*\]))*)/ig;
var match = reg_vars_check.exec( value );
var prop = match[ 1 ], sub_prop_list = match[ 2 ];
var tmp_prop = vars[ prop ];

if ( !tmp_prop ) {
alert( 'The varible ' + prop + ' of string ' + value + ' is not defined!' );
}
else {
if ( sub_prop_list ) {
var sub_prop_match = null;

do {
sub_prop_match = reg_sub_prop.exec( sub_prop_list );
if ( sub_prop_match ) {
var sub_prop = RegExp.$1 || RegExp.$2;

reg_vars_check.lastIndex = 0;
if ( reg_vars_check.test( sub_prop ) ) {
var tmp = check( sub_prop, vars );
tmp_prop = tmp_prop[ tmp ];
}
else {
var key = RegExp.$1 || RegExp.$2;
var tmp_prop = tmp_prop[ key ];

if ( !tmp_prop ) {
alert( 'The varible ' + key + ' of string ' + value + ' is not defined!' );
}
}
}
}
while ( sub_prop_match );
}
}

return tmp_prop;
};

/**
* Parse the tpl
*/
var parse = function() {
var tpl = this.tpl, vars = this.vars;
var tempStr = trim( tpl );

if ( debug === true ) {
var match = null;

do {
match = reg_vars_debug.exec( tempStr );
if ( match ) {
check( match[ 0 ], vars );
}
} while( match );
}

tempStr = tempStr.replace( reg_tag_beg, 'tpl += \'$1\'; $2' ).replace( reg_tag, '$1 tpl += \'$2\'; $3' ).replace( reg_tag_end, '$1 tpl += \'$2\';');
tempStr = tempStr.replace( reg_output, 'tpl += $1;' ).replace( reg_logic, '$1' );
tempStr = tempStr.replace( reg_vars, "vars[ \"$1\" ]$2" );

tpl = new Function( "vars", "var tpl = ''; " + tempStr + " return tpl;" )( vars );
this.vars = {}, this.tpl = '';

return tpl;
}

function _T( vars, tpl ) {
this.vars = vars;
this.tpl = tpl;
}
_T.prototype = {
constructor: Template,
assign: function( param1, param2 ) {
if ( param2 && typeof param1 === 'string' ) {
this.vars[ param1 ] = param2;
}
else {
if ( typeof param1 === 'object' ) {
for ( var index in param1 ) {
this.vars[ index ] = param1[ index ];
}
}
}
},
display: function( tpl ) {
this.tpl = tpl
return parse.call( this );
},
debug: function( enable ) {
debug = enable || false;
}
}

function Template( vars, tpl ) {
var vars = vars || {};
var tpl = tpl || "";

return new _T( vars, tpl );
}

global.baidu = global.baidu || {};
global.baidu.Template = Template;
} )( window );

  4. 使用方法:

  var trs = [
    {name:"小王",age:11,sex:"男"},
    {name:"小李",age:21,sex:"男"},
    {name:"小张",age:14,sex:"女"},
    {name:"小陈",age:19,sex:"女"},
    {name:"小何",age:45,sex:"男"}
  ];

  var html = shark.Template();
  html.assign( 'trs', trs );
  html.assign( 'href', 'http://www.baidu.com' );
  document.getElementById("tpl").innerHTML = html.display(tpl);

posted on 2012-09-19 23:42  黑铁时代  阅读(370)  评论(0编辑  收藏  举报