jQuery-template.js学习
花了点时间,看了下jQuery-template.js,不多废话,先上结构
jQuery.each({..},function(){})
d({..})
function xx(){}//⾃定义⽅法
结构上⾮常简单,但template插件却提供了不错的模版功能,我们根据API来慢慢看这个框架。
⽹络资源
如果在原型上添加⽅法,这⼀般都是暴露给外部调⽤的API,我们来看⼀下,各个⽅法的流程:
我们先看个例⼦:
HTML结构:
<table id="table1"></table>
js部分:
<script type="text/html" id="template1">
<tr>
<td>${ID}</td>
<td>${Name}</td>
</tr>
</script>
<script type="text/javascript" src="jquery-1.9.1.min.js"></script>
<script type="text/javascript" src="pl.js"></script>
<script type="text/javascript">
var users = [
{
ID: 'think8848',
Name: 'Joseph Chan',
Langs: [
'Chinese',
'English'
]
},
{
ID: 'aCloud',
Name: 'Mary Cheung',
Langs: [
'Chinese',
'French'
]
}
];
$('#template1').tmpl(users).appendTo('#table1')
</script>
可以看到模版被写在了type为text/html的script标签中,其中users是数据元,最后调⽤了⼀个$('#templ
ate1').tmpl(users)将信息写⼊模版,最后⽣成出的信息插⼊dom中,即完成。ok,来看⼀下jQuery原型上的tmpl⽅法
tmpl: function( data, options, parentItem ) {
pl( this[0], data, options, parentItem );//页⾯调⽤的时候的⼊⼝⽅法,这会去调⽤jQuery上的tmpl⽅法
}
进⼊jQuery上的tmpl⽅法
tmpl: function( tmpl, data, options, parentItem ) {
var ret, topLevel = !parentItem;
if ( topLevel ) {
// This is a top-level tmpl call (not from a nested template using {{tmpl}})
parentItem = topTmplItem;//{ key: 0, data: {} }
tmpl = plate[tmpl] || plate( null, tmpl );//根据参数数量,选择性的执⾏plate⽅法,这⾥获得了⼀个先有正则匹配,再经过拼接,最后new Function⽽得到⼀个匿名函数
wrappedItems = {}; // Any wrapped items will be rebuilt, since this is top level
} else if ( !tmpl ) {
// The template item is already associated with DOM - this is a refresh.
// Re-evaluate rendered template for the parentItem
tmpl = pl;
newTmplItems[parentItem.key] = parentItem;
if ( parentItem.wrapped ) {
updateWrapped( parentItem, parentItem.wrapped );
}
// Rebuild, without creating a new template item
return jQuery( build( parentItem, null, pl( jQuery, parentItem ) ));
}
if ( !tmpl ) {
return []; //
}
if ( typeof data === "function" ) {//传进来的数据看是否存在函数
data = data.call( parentItem || {} );
}
if ( options && options.wrapped ) {
updateWrapped( options, options.wrapped );
}
ret = jQuery.isArray( data ) ?
jQuery.map( data, function( dataItem ) {
return dataItem ? newTmplItem( options, parentItem, tmpl, dataItem ) : null;
}) :
[ newTmplItem( options, parentItem, tmpl, data ) ];
//进⼊最后⼀层加⼯
return topLevel ? jQuery( build( parentItem, null, ret ) ) : ret;
}
对于这个例⼦,我们需要看⼀下这段代码的⼏个部分
第⼀个部分:
tmpl = plate[tmpl] || plate( null, tmpl );//根据参数数量,选择性的执⾏plate⽅法,这⾥获得了⼀个先有正则匹配,再经过拼接,最后new Function⽽得到⼀个匿名函数
tmpl参数则是那个写有模版的script对象,根据这个⽅法,我们进⼊plate⽅法。
//这⾥经过⼏次进⼊template⽅法,最终还是将type为text/html的script对象传⼊template⽅法的第⼆个参数中
template: function( name, tmpl ) {
if (tmpl) {
// Compile template and associate with name
if ( typeof tmpl === "string" ) {//如何该参数是⼀个字符串,这⾥⽀持将模版以字符串形式写⼊
// This is an HTML string being passed directly in.
tmpl = buildTmplFn( tmpl );
} else if ( tmpl instanceof jQuery ) {
tmpl = tmpl[0] || {};//获取dom对象否则赋空对象
}
if ( deType ) {//如何该参数是⼀个dom节点// If this is a template block, use cached copy, or generate tmpl function and cache.
tmpl = jQuery.data( tmpl, "tmpl" ) || jQuery.data( tmpl, "tmpl", buildTmplFn( tmpl.innerHTML ));//根据正则⽣成⼀个匿名函数返回// Issue: In IE, if the container element is not a script block, the innerHTML will remove quotes from attr // This means that foo="${x}" will not work if the value of x includes white space: foo="${x}" -> foo=value of x.
// To correct this, include space in tag: foo="${ x }" -> foo="value of x"
}
return typeof name === "string" ? (plate[name] = tmpl) : tmpl;//plate⽅法返回了这个匿名函数,将匿名函数分装在plate[name]中便于以后调⽤
}
// Return named compiled template
return name ? (typeof name !== "string" ? plate( null, name ):
(plate[name] ||
// If not in map, and not containing at least on HTML tag, treat as a selector.
// (If integrated with core, )
}
这段代码中的⼀些逻辑判断,会在后⾯的API描述中介绍,我们先看到⼀个很重要的⾃定义⽅法buildTmplFn,这算是这个插件⽐较重要的⼀个部分。传⼊参数则是模版字符串
buildTmplFn:
function buildTmplFn( markup ) {
//注意这⾥在return之前,会将Function构造器⾥的字符串⽣成匿名函数,注意这⾥的写法
return new Function("jQuery","$item",
// Use the variable __ to hold a string array while building the compiled template. (See github/jquery/jquery-tmpl/issues#issue/10).
"var $=jQuery,call,__=[],$data=$item.data;" +
// Introduce the data as local variables using with(){}
"with($data){__.push('" +
// Convert the template into pure JavaScript
.replace( /([\\'])/g, "\\$1" )//将\或者'前⾯都添加⼀个转义符\
.replace( /[\r\t\n]/g, " " )//将空格符全部转成空字符串
.replace( /\$\{([^\}]*)\}/g, "{{= $1}}" )//将类似${name}这种写法的转成{{=name}},换句话说,在页⾯script中也可以使⽤${name}来赋值,这⾥都会统⼀转成{{=name}}格式
.replace( /\{\{(\/?)(\w+|.)(?:\(((?:[^\}]|\}(?!\}))*?)?\))?(?:\s+(.*?)?)?(\(((?:[^\}]|\}(?!\}))*?)\))?\s*\}\}/g,
//replace的⾃定义⽅法中的参数个数表明正则所匹配的分组成员的个数,⼀般第⼀个参数是匹配的整个字符串,也就是说,上⾯的这条正则分组成员应该是6个
function( all, slash, type, fnargs, target, parens, args ) {
/*
* type表⽰你具体需要显⽰的⽂本功能,我们这个例⼦是=,表⽰仅仅是显⽰
* */
var tag = pl.tag[ type ], def, expr, exprAutoFnDetect;
if ( !tag ) {//如何插件中不存在相应配置,抛出异常
throw "Unknown template tag: " + type;
}
def = tag._default || [];
if ( parens && !/\w$/.test(target)) {
target += parens;//拼接主⼲信息
parens = "";
}
//从正则的匹配来看,这个target是我们匹配获得的主要成员
if ( target ) {
target = unescape( target );//去转义符
args = args ? ("," + unescape( args ) + ")") : (parens ? ")" : "");
/
/ Support for target being things LowerCase();
// In that case don't call with template item as 'this' pointer.
//以下两种⽅法主要拼接字符串,最后转成函数执⾏。
expr = parens ? (target.indexOf(".") > -1 ? target + unescape( parens ) : ("(" + target + ").call($item" + args)) : target;
exprAutoFnDetect = parens ? expr : "(typeof(" + target + ")==='function'?(" + target + ").call($item):(" + target + "))";
} else {
exprAutoFnDetect = expr = def.$1 || "null";
}
fnargs = unescape( fnargs );//去转义符
//return的时候,再进⾏⼀次拼接,这⾥源码采⽤占位符的⽅式,先split再join的⽅式实现替换,⼤家也
可以尝试使⽤正则替换。⽐较⽐较执⾏效率
return "');" +
tag[ slash ? "close" : "open" ]
.split( "$notnull_1" ).join( target ? "typeof(" + target + ")!=='undefined' && (" + target + ")!=null" : "true" )//这种⽅法可以学习⼀下,先使⽤占位符站住你需要替换的信息,然后使⽤split分隔开成数组,再使⽤join⽅法加⼊参数合成字                            .split( "$1a" ).join( exprAutoFnDetect )//将之前拼接好的字符串替换占位符$1a
.split( "$1" ).join( expr )//替换$1
.split( "$2" ).join( fnargs || def.$2 || "" ) +//依旧是替换
"__.push('";
}) +
"');}return __;"
)
;
}
其实这个⽅法的作⽤就是根据内置正则表达式,解析模版字符串,截取相应的数据,拼凑成⼀个以后使⽤的匿名函数。这个匿名函数的功能主要将我们之后传⼊的数据源users根
据正则解析,加⼊到模版字符串中。既然正则是这个⽅法的核⼼,那我们就来看⼀下这些正则,前⼏个正则⽐较简单,最后⼀个正则⽐较复杂,我们将它做拆解来理解。
/*
*          \{\{                                --匹配{{
*          (\/?)                                --优先匹配/,捕捉匹配结果                                ($1)slash
*          (\w+|.)                              --优先匹配字符,捕获匹配结果                              ($2)type
*          (?:                                  --匹配但不捕获
*              \(                              --匹配(
*              (                                --捕获匹配结果                                          ($3)fnargs
*                  (?:                          --匹配但不捕捉
*                      [^\}]|\}                --优先匹配⾮},如果有},要求匹配这个}后⾯不能再出现}
*                      (?!\})                  --否定顺序环视,不能存在}
*                  )*?                          --⾮优先匹配设定,尽可能少的去匹配
*              )?                              --优先匹配
*              \)                              --匹配)
*          )?                                  --优先匹配
*          (?:                                  --匹配但不捕捉
*              \s+                              --优先匹配,匹配空格符,⾄少⼀个
*              (.*?)?                          --⾮优先设定,尽可能少的去匹配,但必须要尽量尝试。        ($4)target
*          )?                                  --优先匹配
*          (                                    --捕获匹配结果                                          ($5)parens
*              \(                              --匹配(
*              (                                --捕获匹配结果                                          ($6)args
*                  (?:                          --匹配但不捕获
*                      [^\}]|\}                --优先匹配⾮},如果有},要求匹配这个}后⾯不能再出现}
*                      (?!\})                  --否定顺序环视,不能存在}
*                  )*?                          --⾮优先匹配设定,尽可能少的去匹配
*              )
*              \)                              --匹配)
*            )?                                  --优先匹配
*            \s*                                --优先匹配,空⽩符
*            \}\}                                --匹配}}
*            /g                                  --全局匹配
js 正则替换*
*
因为replace的解析函数中⼀共有7个参数,除了第⼀个参数表⽰全部匹配外,其他都是分组内的匹配。我在注释中都⼀⼀列出,⽅便我们阅读。观察⼀下正则,我们可以了解这
个插件给与我们的⼀些语法使⽤,⽐如说:
页⾯模版内可以这样写:
${name}
{{= name}}
这两种写法都是对的,为什么前⼀条正则就是将${name}转成{{= name}},另外为什么=与name之间需要有空格呢?其实答案在正则⾥,看⼀下($4)target匹配的前⼀段是\s+,这
表明必须⾄少要匹配⼀个空格符。先将我们缩写的格式转成{{= xx}}再根据(.*?)?查出xx的内容,也就是name,其实正则的匹配过程并不是像我所说的这样,在js中的正则在量
词的出现时,会进⾏优先匹配,然后再慢慢回溯,我这样只是形象的简单说⼀下。对于这条正则,我们在后续的API中继续延伸。
对于另外⼀个让我们学习的地⽅,那就是使⽤占位符插⼊我们所要的信息,⼀般我们都会使⽤正则,本插件也提供了⼀种不错的思路。先使⽤占位符,然后通过split(占位符)来分隔字符串,最后使⽤join(信息)来再次拼接字符串。这两个⽅法都是原⽣的,效率的话,我不太确定,应该还不错,有兴趣的朋友可以写写正则,在不同浏览器下⽐⽐看,谁的效率更⾼⼀点。
既然它⽣成了⼀个匿名函数,我们可以简单地打印⼀下看看:
function anonymous(jQuery, $item) {
var $=jQuery,call,__=[],$data=$item.data;
with($data){__.push('<tr>        <td>');
if(typeof(ID)!=='undefined' && (ID)!=null){
__.push($.encode((typeof(ID)==='function'?(ID).call($item):(ID))));
}
__.push('</td>        <td>');
if(typeof(Name)!=='undefined' && (Name)!=null){
__.push($.encode((typeof(Name)==='function'?(Name).call($item):(Name))));
}
__.push('</td>    </tr>');}return __;
}
这⾥with有延长作⽤域的作⽤,在⼀般的开发中,不建议使⽤,不太易于维护,那这个with括号⾥的ID,Name其实都是$data.ID和$data.Name,在没有调⽤这个匿名函数之前,我们先简单看⼀下,传⼊的$item参数拥有data属性,如果这个data的ID和Name不是函数的话就正常显⽰,如果是函数的话,
则这些⽅法需要通过$item来调⽤。另外匿名函数中也拥有了这钱我们所写的模版结构,后续的⼯作就是⽤真实的数据去替换占位符,前提⾮空。ok,回到jQuery的tmpl⽅法中,我们再看⼀个⽐较重要的部分。
ret = jQuery.isArray( data ) ?
jQuery.map( data, function( dataItem ) {
return dataItem ? newTmplItem( options, parentItem, tmpl, dataItem ) : null;
}) :
[ newTmplItem( options, parentItem, tmpl, data ) ];
data是⽤户传⼊的信息元,就是users,是⼀个数组,调⽤jQuery.map来进⾏遍历,来调⽤newTmplItem⽅法,其中tmpl则是刚才我们⽣成的匿名函数。
function newTmplItem( options, parentItem, fn, data ) {
// Returns a template item data structure for a new rendered instance of a template (a 'template item').
// The content field is a hierarchical array of strings and nested items (to be
// removed and replaced by nodes field of dom elements, once inserted in DOM).
var newItem = {
data: data || (data === 0 || data === false) ? data : (parentItem ? parentItem.data : {}),
_wrap: parentItem ? parentItem._wrap : null,
tmpl: null,
parent: parentItem || null,
nodes: [],
calls: tiCalls,
nest: tiNest,
wrap: tiWrap,
html: tiHtml,
update: tiUpdate
};
if ( options ) {
}
if ( fn ) {
// Build the hierarchical content to be used during insertion into DOM
newItem._ctnt = newItem._ctnt || pl( jQuery, newItem );
newItem.key = ++itemKey;//表⽰计数
/
/ Keep track of new template item, until it is stored as jQuery Data on DOM element
(stack.length ? wrappedItems : newTmplItems)[itemKey] = newItem;//这⾥考虑⼀个页⾯可能多处使⽤模版,这⾥进⾏的编号,封装。
}
return newItem;//最后返回这个newItem对象
}
如果看到newItem的定义⽅式,或许之前我们对匿名函数的猜测有了⼀些佐证,没错,最后通过pl(jQuery,newItem)来调⽤了这个匿名函数,这个⽅法除了调⽤执⾏了匿名函数,还简单的封装了⼀下,便于以后我们调⽤$.tmplItem来获取相应的数据元信息。
将⽣成好的ret传⼊最后⼀个加⼯⽅法build,完成整个模版的赋值
//将函数等细化出来,拼接成字符串
function build( tmplItem, nested, content ) {
/
/ Convert hierarchical content into flat string array
// and finally return array of fragments ready for DOM insertion
var frag, ret = content ? jQuery.map( content, function( item ) {
//给所有标签加上_tmplitem=key的属性,也就是这条正则的含义
return (typeof item === "string") ?
// Insert template item annotations, to be converted to jQuery.data( "tmplItem" ) when elems are inserted into DOM.
(tmplItem.key ? place( /(<\w+)(?=[\s>])(?![^>]*_tmplitem)([^>]*)/g, "$1 " + tmplItmAtt + "=\"" + tmplItem.key + "\" $2" ) : item) :
// This is a child template item. Build nested template.
build( item, tmplItem, item._ctnt );
}) :
// If content is not defined, insert tmplItem directly. Not a template item. May be a string, or a string array, e.g. from {{html $item.html()}}.
tmplItem;
if ( nested ) {
return ret;
}
// top-level template
ret = ret.join("");//⽣成最终的模版
// Support templates which have initial or final text nodes, or consist only of text
// Also support HTML entities within the HTML markup.
//这条正则⽐较简单,我们来看过⼀下。获得<>内的主要信息
frag = jQuery( middle ).get();//将⽣成的jQuery dom对象转成数组集合,集合的每个成员则是对应⽣成的jQuery对象的原⽣dom对象
//解析⽣成出来的dom
storeTmplItems( frag );
if ( before ) {
frag = unencode( before ).concat(frag);
}
if ( after ) {
frag = at(unencode( after ));
}
});
return frag ? frag : unencode( ret );
}
这个⾥⾯出现了两条正则,我们分别看⼀下:
*
*      /
*      (                                        --匹配捕获($1)
*          <\w+                                --匹配<,字母或数字或下划线或汉字(⾄少⼀个,优先匹配)(存在固化分组的含义)
*      )
*      (?=[\s>])                                --顺序环视,后⾯必须有空格和⼀个>
*      (?![^>]*_tmplitem)                      --顺序否定环视,后⾯不能有⾮>字符,还有_tmplitem这些字符串
*      (                                        --匹配捕获($2)
*          [^>]*                                --匹配⾮>字符,优先匹配,任意多个
*      )
*      /g                                      --全局匹配
*
*
*      ^                                        --开始
*      \s*                                      --优先匹配,任意多空⽩符
*      (                                        --匹配捕获                                ($1)before
*          [^<\s]                              --匹配⾮<;或者是空⽩符
*          [^<]*                                --优先匹配,匹配⾮<
*      )?                                      --优先匹配
*      (                                        --匹配捕获                                ($2)middle
*          <[\w\W]+>                            --匹配<,任意字符(⾄少⼀个,优先匹配),>
*      )
*      (                                        --匹配捕获                                ($3)after
*          [^>]*                                --匹配⾮>
*          [^>\s]                              --匹配⾮>或者是空⽩符
*      )?                                      --优先匹配(0,1次)
*      \s*                                      --匹配空⽩符(任意次,优先匹配)
*      $                                        --结束
*
*
前⼀个正则的作⽤是给标签加上_tmplitem=key的属性,后⼀条正则则是获得<>内的主要信息。最后进⼊storeTmplItems⽅法
function storeTmplItems( content ) {
var keySuffix = "_" + cloneIndex, elem, elems, newClonedItems = {}, i, l, m;
for ( i = 0, l = content.length; i < l; i++ ) {
if ( (elem = content[i]).nodeType !== 1 ) {//如果该节点不是元素节点,则直接跳过
continue;
}
//这⾥将会到关键的⼏个元素节点,在模版中可能会存在注释节点,⽂本节点。
//遍历元素节点
elems = ElementsByTagName("*");
for ( m = elems.length - 1; m >= 0; m-- ) {//⾃减的遍历有时候⽐⾃增要好很多
processItemKey( elems[m] );
}
processItemKey( elem );
}
作为储存节点的⽅法,使⽤processItemKey进⾏遍历。
function processItemKey( el ) {
var pntKey, pntNode = el, pntItem, tmplItem, key;
// Ensure that each rendered template inserted into the DOM has its own template item,
//确保每个呈现模板插⼊到DOM项⽬有⾃⼰的模板
if ( (key = el.getAttribute( tmplItmAtt ))) {//查看这个元素上是否有_tmplitem这个属性,限定了属于某个模版的内容
while ( pntNode.parentNode && (pntNode = pntNode.parentNode).nodeType === 1 && !(pntKey = Attribute( tmplItmAtt ))) { }//这种写法也⽐较不错,使⽤while不停向上查询pntNode的⽗节
点if ( pntKey !== key ) {//⽗节点存在,但是没有_tmplitem这个属性,⼀般是⽂档碎⽚
// The next ancestor with a _tmplitem expando is on a different key than this one.
// So this is a top-level element within this template item
// Set pntNode to the key of the parentNode, or to 0 if pntNode.parentNode is null, or pntNode is a fragment.
//如果该元素的⽗节点不存在,则可能是⽂档碎⽚
pntNode = pntNode.parentNode ? (deType === 11 ? 0 : (Attribute( tmplItmAtt ) || 0)) : 0;
if ( !(tmplItem = newTmplItems[key]) ) {
// The item is for wrapped content, and was copied from the temporary parent wrappedItem.
tmplItem = wrappedItems[key];
tmplItem = newTmplItem( tmplItem, newTmplItems[pntNode]||wrappedItems[pntNode] );
tmplItem.key = ++itemKey;
newTmplItems[itemKey] = tmplItem;
}
if ( cloneIndex ) {
cloneTmplItem( key );
}
}
} else if ( cloneIndex && (tmplItem = jQuery.data( el, "tmplItem" )) ) {
//这是⼀个元素,呈现克隆在附加或appendTo等等
//TmplItem存储在jQuery cloneCopyEvent数据已经被克隆。我们必须换上新鲜的克隆tmplItem。
/
/ This was a rendered element, cloned during append or appendTo etc.
// TmplItem stored in jQuery data has already been cloned in cloneCopyEvent. We must replace it with a fresh cloned tmplItem.
cloneTmplItem( tmplItem.key );
newTmplItems[tmplItem.key] = tmplItem;
pntNode = jQuery.data( el.parentNode, "tmplItem" );
pntNode = pntNode ? pntNode.key : 0;
}
if ( tmplItem ) {//遍历到最外层的元素
pntItem = tmplItem;
//到⽗元素的模板项。
// Find the template item of the parent element.
// (Using !=, not !==, since pntItem.key is number, and pntNode may be a string)
while ( pntItem && pntItem.key != pntNode ) {//顶级为pntNode为0
// Add this element as a top-level node for this rendered template item, as well as for any
// ancestor items between this item and the item of its parent element
pntItem = pntItem.parent;//向上迭代
}
// Delete content built during rendering - reduce API surface area and memory use, and avoid exposing of stale data
delete tmplItem._ctnt;//删除属性
delete tmplItem._wrap;//删除属性
// Store template item as jQuery data on the element
jQuery.data( el, "tmplItem", tmplItem );//这样可以$(el).data('tmplItem')读取tmplItem的值
}
function cloneTmplItem( key ) {
key = key + keySuffix;
tmplItem = newClonedItems[key] =
(newClonedItems[key] || newTmplItem( tmplItem, newTmplItems[tmplItem.parent.key + keySuffix] || tmplItem.parent ));
}
}
根据之前添加的_tmplitem属性,做了完整的向上遍历查,最后删除掉_tmplitem属性。build⽅法将frag参数uncode之后返回给pl⽅法来返回,最后通过appendTo加⼊到dom中,⽣成我们所看到的结果。以上通过⼀个简单的例⼦粗略的过了⼀下插件的运⾏流程,我们来看⼀些官⽅的API。
1.$.template,将HTML编译成模版
例⼦1
var markup = '<tr><td>${ID}</td><td>${Name}</td></tr>';
$.template('template', markup);
$.tmpl('template', users).appendTo('#templateRows');
直接看⼀下$.template⽅法
if ( typeof tmpl === "string" ) {//如何该参数是⼀个字符串,这⾥⽀持将模版以字符串形式写⼊
// This is an HTML string being passed directly in.
tmpl = buildTmplFn( tmpl );
}
可以看到,我们传⼊的markup是⼀个字符串,直接将这个markup传⼊buildTmplFn中去⽣成⼀个匿名函数。
return typeof name === "string" ? (plate[name] = tmpl) : tmpl;//plate⽅法返回了这个匿名函数,将匿名函数分装在plate[name]中便于以后调⽤
插件内部将编译好的HTML模版的匿名函数存⼊了plate[name]中,便于我们以后调⽤。
tmpl = plate[tmpl] || plate( null, tmpl );//根据参数数量,选择性的执⾏plate⽅法,这⾥获得了⼀个先有正则匹配,再经过拼接,最后new Function⽽得到⼀个匿名函数
这⾥插件先查了plate看是否存在tmpl的已经⽣成好的匿名函数,有则直接使⽤,否则重新⽣成。获得了匿名函数,其他步骤跟之前⼀样。
pl()有两个⽐较有⽤的参数$item,$data,其中$item表⽰当前模版,$data表⽰当前数据
例⼦2
<script type="text/html" id="template1">
<tr>
<td>${ID}</td>
<td>${$data.Name}</td>
<td>${$Langs(';')}</td>
</tr>
</script>
var users = [
{
ID: 'think8848',
Name: 'Joseph Chan',
Langs: [
'Chinese',
'English'
]
},
{
ID: 'aCloud',
Name: 'Mary Cheung',
Langs: [
'Chinese',
'French'
]
}
]
$('#template1').tmpl(users,{
getLangs: function(separator){
return this.data.Langs.join(separator);
}
}).appendTo('#table1');
<table id="table1"></table>
乍⼀看,调⽤的⽅式是⼀样的,你会疑问为什么模版⾥要⽤$item和$data这样的形式,其实你仔细看⼀下上个例⼦⽣成的匿名函数,就能发现这⾥这么写其实是为了更好的拼
接。以下是这个例⼦所⽣成的匿名函数:
function anonymous(jQuery, $item) {
var $=jQuery,call,__=[],$data=$item.data;with($data){__.push('<tr>        <td>');if(typeof(ID)!=='undefined' && (ID)!=null){__.push($.encode((typeof(ID)==='function'?(ID).call($item):(ID))));}__.push('</td>        <td>');if(typeof($data.Name)!=='undefin $data是$item的⼀
个属性,存储着数据,$item中同样有很多⾃定义⽅法。这⾥getLangs⽅法⾥的this在匿名函数具体调⽤的时候会指向$item,这⾥需要注意⼀下。在
newTmplItem⽅法⾥执⾏我们⽣成的匿名函数,这⾥都没有什么问题,这⾥我们通过正则简单回看⼀下这个${ID},${$data.Name}是如何匹配的。这两个匹配其实是⼀个道理,
匹配的正则如下:
/\{\{(\/?)(\w+|.)(?:\(((?:[^\}]|\}(?!\}))*?)?\))?(?:\s+(.*?)?)?(\(((?:[^\}]|\}(?!\}))*?)\))?\s*\}\}/g
⼤家对照我之前的分解表看⽐较⽅便。我们拿${$data.Name}举例,不过使⽤之前,它已经转成{{= $data.Name}}
1:匹配{{
2:尝试匹配(\/?),?表⽰优先匹配,但是{{后⾯没有/,所以匹配⽆效,?表⽰最多成功匹配⼀个。继续后⾯的匹配
3:尝试匹配(\w+|.),如果|左边的能匹配成功则不需要进⾏右边的匹配,所以\w+会尽可能去匹配,但是\w⽆法匹配=所以,尝试⽤|右边的.去匹配,.可以匹配=,因为没有量词,
所以只能匹配这个=
4:尝试匹配(?:\(((?:[^\}]|\}(?!\}))*?)?\))?
  4.1:(?:)表⽰匹配但不捕获,其⾥⾯第⼀个要匹配的是(,可以看到{{=后⾯是空格⽽不是(所以匹配失败,加上这不捕获的分组使⽤的是优先量词?,允许匹配为空,继续后⾯
的匹配
5:尝试匹配(?:\s+(.*?)?)?
  5.1:分组⾥第⼀个匹配\s+,匹配=后⾯的空格符号,继续尝试匹配,当匹配到$时发现⽆法匹配,则\s+匹配结束。
  5.2:尝试匹配(.*?)?,分组外围使⽤的是?,尽可能尝试匹配⼀个看看,对于(.*?)匹配$,因为(.*?)是惰性匹配(不优先匹配),所以系统选择不匹配,另外外围的?也允许匹配不
成功。继续后⾯的匹配
6:尝试匹配(\(((?:[^\}]|\}(?!\}))*?)\))?
  6.1:如果4的步骤不匹配,那5中的\(同样⽆法匹配$,所以匹配失败
7:尝试匹配\s*\}\},如果从$开始匹配,果断匹配失败。整个匹配结束了么?其实还没有,开始对惰性匹配继续进⾏匹配
8:让(.*?)先匹配$,再执⾏5,6步骤,如果最终匹配失败了,继续让(.*?)匹配$d,依次类推,直到(.*?)匹配到$data.Name,这时6结果匹配成功。整个正则匹配匹配成功。
以上则是该正则的⼀次简单匹配过程,可以发现该正则使⽤了惰性匹配⼀定程度上减少了正则的回溯次数,提⾼了效率。
3.each的⽤法
例⼦:
<script type="text/html" id="template1">
<li>
ID: ${ID}; Name: ${Name};
<br />Langs:
<ul>
<STRONG>
{{each(i,lang) Langs}}
<li>${i + 1}:
<label>${lang}. </label>
</li>
{{/each}}
</STRONG>
</ul>
</li>
</script>
var users = [
{
ID: 'think8848',
Name: 'Joseph Chan',
Langs: [
'Chinese',
'English'
]
},
{
ID: 'aCloud',

版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。