加入特性:
1、嵌套模板
2、支持渲染过滤器模式{{A | filterA:arg1..}}
3、缓存(考虑加入localstorage?)
4、调试
todo
性能优化
异步渲染
<!-- lang: js -->
(function (_) {
_.templateSettings = {
evaluate : /{%([\s\S]+?)%}/g,
interpolate: /{{([\s\S]+?)}}/g,
escape : /{%-([\s\S]+?)%}/g,
include : /{%include([\s\S]+?)%}/g,
isCache : true,
isDebug : true,
};
// When customizing `templateSettings`, if you don't want to define an
// interpolation, evaluation or escaping regex, we need one that is
// guaranteed not to match.
var noMatch = /(.)^/;
// Certain characters need to be escaped so that they can be put into a
// string literal.
var escapes = {
"'" : "'",
'\\' : '\\',
'\r' : 'r',
'\n' : 'n',
'\t' : 't',
'\u2028': 'u2028',
'\u2029': 'u2029'
};
var escaper = /\\|'|\r|\n|\t|\u2028|\u2029/g;
/**
* 参考template
* add:
* filter,可以存在data、或者全局filters里面
*/
//gloabal filter
var filters = _.filters = {},
//模板缓存类
tplCache = {};
_.addFilters = function (obj, fn) {
if ( typeof obj == 'object' ) {
_.extend(filters, obj);
}
else if ( typeof obj == 'string' ) {
_.extend(filters, {obj: fn});
}
}
_.template2 = function (text, data, settings) {
settings = _.defaults({}, settings, _.templateSettings);
//模板外部引入include
(function replaceInclude() {
text = text.replace(settings.include, function (match, result) {
var url = $.trim(result)
var ret = syncGetTpl(url)
return ret
})
//模板中还有模板
if ( settings.include.test(text) ) {
replaceInclude();
}
})()
// Combine delimiters into one regular expression via alternation.
var matcher = new RegExp([
(settings.escape || noMatch).source,
(settings.interpolate || noMatch).source,
(settings.evaluate || noMatch).source
].join('|') + '|$', 'g');
// Compile the template source, escaping string literals appropriately.
var index = 0;
var source = "__p+='";
text.replace(matcher, function (match, escape, interpolate, evaluate, offset) {
source += text.slice(index, offset)
.replace(escaper, function (match) {
return '\\' + escapes[match];
});
if ( escape ) {
source += "'+\n((__t=(" + escape + "))==null?'':_.escape(__t))+\n'";
}
//解析属性表达式、、支持过滤器
//example:value|fn2:a1,a2|fn3:b1,b2,b3
if ( interpolate ) {
source += "'+\n((__t=(" + getAssembleFnStr() + "))==null?'':__t)+\n'";
}
function getAssembleFnStr() {
var filterChain = interpolate.split('|'), curFilterIdx = -1;
var filterStack = [];
return _(filterChain).reduce(function (val, filter) {
//filter有两种;一种function、另一种functionName
if ( filter.length ) {
curFilterIdx++;
var fname = filter.split(':'),
name = fname[0],
args = fname[1] || '',
callFnstr = new Function('x', 'return x');
//name is function Str
// var nameFn = new Function('return name')()
var exp = '';
if ( name.indexOf('function') > -1 ) {
exp = 'var filterFunc = name;\n' +
'return name.apply(_,_.flatten(['+val+', args.split(",")]))';
}
//debug模式
else if ( name == 'debug' ) {
var beforeFilter = filterChain[curFilterIdx],
beforeFilterName = beforeFilter.split(':')[0],
beforeFilterFn = beforeFilterName.indexOf('function') > -1 ? beforeFilterName : filters[beforeFilterName]
exp =
'\n(function(){\n' +
'var fv = '+val+';\n'+
(settings.isDebug ?
(
"console.debug(\"Filter:" + beforeFilter + "\");\n" +
"console.debug(\"%cOutput:\" +fv" + ",'color:red');\n" +
(curFilterIdx > 0 ? "console.debug(\"FilterFn:" + beforeFilterName + ":\"," + beforeFilterFn + ");\n" : "")
)
: ";" ) +
'\nreturn fv;' +
'\n})()\n';
}
else {
if ( filters[name] ) {
exp = '(' + filters[name] + ')' + '(' + val + (args.length ? (',' + args) : '') + ')'
}
else {
console.error('[' + name + ']filter:doesnt exist!')
exp = val;
}
}
return exp;
}
else {
return val;
}
})
}
if ( evaluate ) {
source += "';\n" + evaluate + "\n__p+='";
}
index = offset + match.length;
return match;
});
source += "';\n";
// If a variable is not specified, place data values in local scope.
if ( !settings.variable ) source = 'with(obj||{}){\n'
+ source +
'}\n';
source = "var __t,__p='',__j=Array.prototype.join," +
"print=function(){__p+=__j.call(arguments,'');};\n" +
source + "return __p;\n";
var render;
try {
render = new Function(settings.variable || 'obj', '_', source);
} catch (e) {
e.source = source;
throw e;
}
if ( data && _.size(data) ) return render(data, _);
var template = function (data) {
return render.call(this, data, _);
};
// Provide the compiled function source as a convenience for precompilation.
template.source = 'function(' + (settings.variable || 'obj') + '){\n' + source + '}';
return template;
};
_.tpl = function (htmlSelectorOrTplUrl, renderData, renderDomSeletor, renderedFn) {
var htmlOriginal = $(htmlSelectorOrTplUrl).html(), t1 = _.now(),
compiledFn = function (html) {
var tpl = _.template2(html);
var t2 = _.now()
$(renderDomSeletor).html(tpl(renderData));
var t3 = _.now()
debug('耗时分析', tpl.source, '\n[ms]compile time', t2 - t1, 'render time:', t3 - t2, 'total', t3 - t1)
renderedFn ? renderedFn.apply(this) : null;
};
if ( $.trim(htmlOriginal) == '' ) {
//ajax请求获取模板
$.get(htmlSelectorOrTplUrl, function (html) {
compiledFn(html)
})
}
else {
compiledFn(htmlOriginal)
}
}
function debug(group) {
if ( _.templateSettings.isDebug ) {
console.group(group);
_([].slice.call(arguments, 1)).each(function (text) {
console.log(text);
})
console.groupEnd();
}
}
function syncGetTpl(htmlSelectorOrTplUrl) {
//如果一个url作为selector进来会抛异常,
//hack:
var html = '';
//cache
if ( _.templateSettings.isCache && tplCache[htmlSelectorOrTplUrl] ) {
return tplCache[htmlSelectorOrTplUrl];
}
try {
if ( $(htmlSelectorOrTplUrl).length ) {
html = $(htmlSelectorOrTplUrl).html();
}
else {
aj();
}
} catch (e) {
aj();
}
function aj() {
$.ajax({
url : htmlSelectorOrTplUrl + '?' + _.now(),
type : 'get',
async : false,
success: function (resp) {
html = resp
}
})
}
if ( _.templateSettings.isCache ) {
tplCache[htmlSelectorOrTplUrl] = html;
}
return html;
}
})(_)