jsPerf.app is an online JavaScript performance benchmark test runner & jsperf.com mirror. It is a complete rewrite in homage to the once excellent jsperf.com now with hopefully a more modern & maintainable codebase.
jsperf.com URLs are mirrored at the same path, e.g:
https://jsperf.com/negative-modulo/2
Can be accessed at:
https://jsperf.app/negative-modulo/2
Revision : update mustache to latest version (0.7.2 was 0.3)
A limited comparison of some popular JavaScript templating engines on a short template: 6 header tags, and 10 list items. Compared templating engines:
<script src="http://ajax.googleapis.com/ajax/libs/jquery/1/jquery.min.js"></script>
<script src="http://documentcloud.github.com/underscore/underscore.js"></script>
<!--NOTE: Need to load from CDNJS since GitHub uses text/plain MIME type, which is blocked in IE9-->
<!--<script src="http://github.com/janl/mustache.js/raw/master/mustache.js"></script>-->
<script src="http://cdnjs.cloudflare.com/ajax/libs/ICanHaz.js/0.10/ICanHaz.min.js"></script>
<script src="https://github.com/downloads/wycats/handlebars.js/handlebars.1.0.0.beta.3.js"></script>
<script src="http://cdn.kendostatic.com/2011.2.804/js/kendo.all.min.js"></script>
<script src="http://jashkenas.github.com/coffee-script/extras/coffee-script.js"></script>
<!--NOTE: Need to load from MSFT CDN since GitHub uses text/plain MIME type, which is blocked in IE9-->
<!--<script src="http://github.com/jquery/jquery-tmpl/raw/master/jquery.tmpl.min.js"></script>-->
<script src="http://ajax.microsoft.com/ajax/jquery.templates/beta1/jquery.tmpl.min.js"></script>
<script src="http://qatrix.com/js/qatrix-1.1.min.js"></script>
<script>
var $ = jQuery.noConflict();
window.sharedVariables = {
header: "Header",
header2: "Header2",
header3: "Header3",
header4: "Header4",
header5: "Header5",
header6: "Header6",
list: ['1', '2', '3', '4', '5', '6', '7', '8', '9', '10']
};
var templateA = function (id, content) {
return template[
typeof content === 'object' ? 'render' : 'compile'
].apply(template, arguments);
};
(function (exports, global) {
//"use strict";
exports.openTag = '<@';
exports.closeTag = '@>';
exports.parser = null;
/**
* 渲染模板
* @name template.render
* @param {String} 模板ID
* @param {Object} 数据
* @return {String} 渲染好的HTML字符串
*/
exports.render = function (id, data) {
var cache = _getCache(id);
if (cache === undefined) {
return _debug({
id: id,
name: 'Render Error',
message: 'Not Cache'
});
}
return cache(data);
};
/**
* 编译模板
* 2012-6-6:
* define 方法名改为 compile,
* 与 Node Express 保持一致,
* 感谢 TooBug 提供帮助!
* @name template.compile
* @param {String} 模板ID (可选)
* @param {String} 模板字符串
* @return {Function} 渲染方法
*/
exports.compile = function (id, source) {
var debug = arguments[2];
if (typeof source !== 'string') {
debug = source;
source = id;
id = null;
}
try {
var Render = _compile(source, debug);
} catch (e) {
e.id = id || source;
e.name = 'Syntax Error';
return _debug(e);
}
function render (data) {
try {
return new Render(data).template;
} catch (e) {
if (!debug) {
return exports.compile(id, source, true)(data);
}
e.id = id || source;
e.name = 'Render Error';
e.source = source;
return _debug(e);
};
};
render.prototype = Render.prototype;
render.toString = function () {
return Render.toString();
};
if (id) {
_cache[id] = render;
}
return render;
};
/**
* 扩展模板公用辅助方法
* @name template.helper
* @param {String} 名称
* @param {Function} 方法
*/
exports.helper = function (name, helper) {
_helpers[name] = helper;
};
var _cache = {};
var _isNewEngine = ''.trim;
var _isServer = _isNewEngine && !global.document;
var _keyWordsMap = {};
var forEach = Array.prototype.forEach;
var _forEach = function (array, callback) {
forEach.call(array, callback);
};
var _create = Object.create || function (object) {
function Fn () {};
Fn.prototype = object;
return new Fn;
};
var _helpers = exports.prototype = {
$forEach: _forEach,
$render: exports.render,
$getValue: function (value) {
return value === undefined ? '' : value;
}
};
// javascript 关键字表
_forEach((
// 关键字
'break,case,catch,continue,debugger,default,delete,do,else,false,finally,for,function,if'
+ ',in,instanceof,new,null,return,switch,this,throw,true,try,typeof,var,void,while,with'
// 保留字
+ ',abstract,boolean,byte,char,class,const,double,enum,export,extends,final,float,goto'
+ ',implements,import,int,interface,long,native,package,private,protected,public,short'
+ ',static,super,synchronized,throws,transient,volatile'
// ECMA 5 - use strict
+ ',arguments,let,yield'
).split(','), function (key) {
_keyWordsMap[key] = true;
});
// 模板编译器
var _compile = function (source, debug) {
var openTag = exports.openTag;
var closeTag = exports.closeTag;
var parser = exports.parser;
var code = source;
var tempCode = '';
var line = 1;
var uniq = {$out:true,$line:true};
var variables = "var $helpers=this,"
+ (debug ? "$line=0," : "");
var replaces = _isNewEngine
? ["$out='';", "$out+=", ";", "$out"]
: ["$out=[];", "$out.push(", ");", "$out.join('')"];
var concat = _isNewEngine
? "if(content!==undefined){$out+=content;return content}"
: "$out.push(content);";
var print = "function(content){" + concat + "}";
var include = "function(id,data){"
+ "if(data===undefined){data=$data}"
+ "var content=$helpers.$render(id,data);"
+ concat
+ "}";
// html与逻辑语法分离
_forEach(code.split(openTag), function (code, i) {
code = code.split(closeTag);
var $0 = code[0];
var $1 = code[1];
// code: [html]
if (code.length === 1) {
tempCode += html($0);
// code: [logic, html]
} else {
tempCode += logic($0);
if ($1) {
tempCode += html($1);
}
}
});
code = tempCode;
// 调试语句
if (debug) {
code = 'try{' + code + '}catch(e){'
+ 'e.line=$line;'
+ 'throw e'
+ '}';
}
code = variables + replaces[0] + code + 'this.template=' + replaces[3];
try {
var render = new Function('$data', code);
var proto = render.prototype = _create(_helpers);
proto.toString = function () {
return this.template;
};
return render;
} catch (e) {
e.temp = 'function anonymous($data) {' + code + '}';
throw e;
};
// 处理 HTML 语句
function html (code) {
// 记录行号
line += code.split(/\n/).length - 1;
code = code
// 单双引号与反斜杠转义
.replace(/('|"|\\)/g, '\\$1')
// 换行符转义(windows + linux)
.replace(/\r/g, '\\r')
.replace(/\n/g, '\\n');
code = replaces[1] + "'" + code + "'" + replaces[2];
return code + '\n';
};
// 处理逻辑语句
function logic (code) {
var thisLine = line;
if (parser) {
// 语法转换器
code = parser(code);
} else if (debug) {
// 记录行号
code = code.replace(/\n/g, function () {
line ++;
return '$line=' + line + ';';
});
}
// 输出语句
if (code.indexOf('-') === 0) {
code = code.substring(1).replace(/[\s;]*$/, '');
if (_isNewEngine) {
// $getValue: undefined 转化为空字符串
code = '$getValue(' + code + ')';
}
code = replaces[1] + code + replaces[2];
}
if (debug) {
code = '$line=' + thisLine + ';' + code;
}
getKey(code);
return code + '\n';
};
// 提取模板中的变量名
function getKey (code) {
// 过滤注释、字符串、方法名
code = code.replace(/\/\*.*?\*\/|'[^']*'|"[^"]*"|\.[\$\w]+/g, '');
// 分词
_forEach(code.split(/[^\$\w\d]+/), function (name) {
// 沙箱强制语法规范:禁止通过套嵌函数的 this 关键字获取全局权限
if (/^this$/.test(name)) {
throw {
message: 'Prohibit the use of the "' + name + '"'
};
}
// 过滤关键字与数字
if (!name || _keyWordsMap.hasOwnProperty(name) || /^\d/.test(name)) {
return;
}
// 除重
if (!uniq.hasOwnProperty(name)) {
setValue(name);
uniq[name] = true;
}
});
};
// 声明模板变量
// 赋值优先级: 内置特权方法(include, print) > 公用模板辅助方法 > 数据
function setValue (name) {
var value;
var origins = ",Math,encodeURI,encodeURIComponent,decodeURI,decodeURIComponent,escape,unescape,eval\
,isNaN,isFinite,parseInt,parseFloat\
,undefined,NaN,Infinity,Object,Array,Function,Boolean,String,Number,Date,RegExp,Error,";
if(~origins.indexOf(","+name+",")){
return;
}
if (name === 'print') {
value = print;
} else if (name === 'include') {
value = include;
} else if (_helpers.hasOwnProperty(name)) {
value = '$helpers.' + name;
} else {
value = '$data.' + name;
}
variables += name + '=' + value + ',';
};
};
// 获取模板缓存
var _getCache = function (id) {
var cache = _cache[id];
if (cache === undefined && !_isServer) {
var elem = document.getElementById(id);
if (elem) {
exports.compile(id, elem.value || elem.innerHTML);
}
return _cache[id];
} else if (_cache.hasOwnProperty(id)) {
return cache;
}
};
// 模板调试器
var _debug = function (e) {
var content = '[template]:\n'
//+ e.id
+ '\n[name]:\n'
+ e.name;
if (e.message) {
content += '\n[message]:\n'
+ e.message;
}
if (e.line) {
content += '\n[line]:\n'
+ e.line;
content += '\n[source]:\n'
+ e.source.split(/\n/)[e.line - 1].replace(/^[\s\t]+/, '');
}
if (e.temp) {
content += '\n[temp]:\n'
+ e.temp;
}
function error () {
return error + '';
};
error.toString = function () {
return '{Template Error}'+"\n"+content;
};
return error;
};
})(templateA, this);
window.jQueryTemplate = $.template(null, "<div><h1 class='header'>{{html header}}</h1><h2 class='header2'>{{html header2}}</h2><h3 class='header3'>{{html header3}}</h3><h4 class='header4'>{{html header4}}</h4><h5 class='header5'>{{html header5}}</h5><h6 class='header6'>{{html header6}}</h6><ul class='list'>{{each list}}<li class='item'>{{html $value}}</li>{{/each}}</ul></div>");
window.mustacheTemplate = "<div><h1 class='header'>{{{header}}}</h1><h2 class='header2'>{{{header2}}}</h2><h3 class='header3'>{{{header3}}}</h3><h4 class='header4'>{{{header4}}}</h4><h5 class='header5'>{{{header5}}}</h5><h6 class='header6'>{{{header6}}}</h6><ul class='list'>{{#list}}<li class='item'>{{{.}}}</li>{{/list}}</ul></div>";
window.handlebarsTemplate = Handlebars.compile("<div><h1 class='header'>{{header}}</h1><h2 class='header2'>{{header2}}</h2><h3 class='header3'>{{header3}}</h3><h4 class='header4'>{{header4}}</h4><h5 class='header5'>{{header5}}</h5><h6 class='header6'>{{header6}}</h6><ul class='list'>{{#each list}}<li class='item'>{{this}}</li>{{/each}}</ul></div>");
window.kendouiTemplate = kendo.template("<div><h1 class='header'><#= data.header #></h1><h2 class='header2'><#= data.header2 #></h2><h3 class='header3'><#= data.header3 #></h3><h4 class='header4'><#= data.header4 #></h4><h5 class='header5'><#= data.header5 #></h5><h6 class='header6'><#= data.header6 #></h6><ul class='list'><# for (var i = 0, l = data.list.length; i < l; i++) { #><li class='item'><#= data.list[i] #></li><# } #></ul></div>", {useWithBlock:true});
window.underscoreTemplate = _.template("<div><h1 class='header'><%= header %></h1><h2 class='header2'><%= header2 %></h2><h3 class='header3'><%= header3 %></h3><h4 class='header4'><%= header4 %></h4><h5 class='header5'><%= header5 %></h5><h6 class='header6'><%= header6 %></h6><ul class='list'><% for (var i = 0, l = list.length; i < l; i++) { %><li class='item'><%= list[i] %></li><% } %></ul></div>");
window.baseHtml = "<div><h1 class='header'></h1><h2 class='header2'></h2><h3 class='header3'></h3><h4 class='header4'></h4><h5 class='header5'></h5><h6 class='header6'></h6><ul class='list'><li class='item'></li></ul></div>";
//Resig Template Function (modified to support ')
function tmpl(str) {
var strFunc =
"var p=[];" +
"with(obj){p.push('" +
str.replace(/[\r\t\n]/g, " ")
.replace(/'(?=[^#]*#>)/g, "\t")
.split("'").join("\\'")
.split("\t").join("'")
.replace(/<#=(.+?)#>/g, "',$1,'")
.split("<#").join("');")
.split("#>").join("p.push('")
+ "');}return p.join('');";
return new Function("obj", strFunc);
}
window.resig = tmpl("<div><h1 class='header'><#= header #></h1><h2 class='header2'><#= header2 #></h2><h3 class='header3'><#= header3 #></h3><h4 class='header4'><#= header4 #></h4><h5 class='header5'><#= header5 #></h5><h6 class='header6'><#= header6 #></h6><ul class='list'><# for (var i = 0, l = list.length; i < l; i++) { #><li class='item'><#= list[i] #></li><# } #></ul></div>");
window.qatrixTemplate = Qatrix.$template("<div><h1 class='header'>{{header}}</h1><h2 class='header2'>{{header2}}</h2><h3 class='header3'>{{header3}}</h3><h4 class='header4'>{{header4}}</h4><h5 class='header5'>{{header5}}</h5><h6 class='header6'>{{header6}}</h6></div>");
window.AAA = templateA.compile("<div><h1 class='header'><@-header@></h1><h2 class='header2'><@-header2@></h2><h3 class='header3'><@-header3@></h3><h4 class='header4'><@-header4@></h4><h5 class='header5'><@-header5@></h5><h6 class='header6'><@-header6@></h6></div>");
</script>
Ready to run.
Test | Ops/sec | |
---|---|---|
Mustache |
| ready |
jQuery.template |
| ready |
Kendo UI |
| ready |
Handlebars |
| ready |
Underscore |
| ready |
ART |
| ready |
Qatrix |
| ready |
You can edit these tests or add more tests to this page by appending /edit to the URL.