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
<!-- Targeted DIV -->
<div id="target" style="display: none;"></div>
<!-- Libraries -->
<script src="https://code.jquery.com/jquery-git2.js"></script>
<script src="https://rawgithub.com/eugenioclrc/handlehash.js/master/HandleHash.js"></script>
<script src="http://underscorejs.org/underscore-min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/mustache.js/0.7.2/mustache.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/handlebars.js/1.2.1/handlebars.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/hogan.js/2.0.0/hogan.min.js"></script>
<script src="https://rawgithub.com/olado/doT/master/doT.js"></script>
<script src="https://gsf.github.io/whiskers.js/whiskers.min.js"></script>
<script src="https://qatrix.com/js/qatrix-1.1.min.js"></script>
<script src="https://rawgithub.com/premasagar/875670/raw/d52752ead19a4eebc7237602438ae08a2541a5b5/tim-lite-cached-min.js"></script>
<script src="http://fb.me/react-0.8.0.min.js"></script>
<script src="https://cdn.rawgit.com/adammark/Markup.js/master/src/markup.min.js"></script>
<script src="https://cdn.rawgit.com/foo123/Contemplate/master/src/js/Contemplate.min.js"></script>
<script>
/*
********** Juicer **********
${A Fast template engine}
Project Home: http://juicer.name
Author: Guokai
Gtalk: badkaikai@gmail.com
Blog: http://benben.cc
Licence: MIT License
Version: 0.6.8-stable
*/
(function() {
// This is the main function for not only compiling but also rendering.
// there's at least two parameters need to be provided, one is the tpl,
// another is the data, the tpl can either be a string, or an id like #id.
// if only tpl was given, it'll return the compiled reusable function.
// if tpl and data were given at the same time, it'll return the rendered
// result immediately.
var juicer = function() {
var args = [].slice.call(arguments);
args.push(juicer.options);
if(args[0].match(/^\s*#([\w:\-\.]+)\s*$/igm)) {
args[0].replace(/^\s*#([\w:\-\.]+)\s*$/igm, function($, $id) {
var _document = document;
var elem = _document && _document.getElementById($id);
args[0] = elem ? (elem.value || elem.innerHTML) : $;
});
}
if(typeof(document) !== 'undefined' && document.body) {
juicer.compile.call(juicer, document.body.innerHTML);
}
if(arguments.length == 1) {
return juicer.compile.apply(juicer, args);
}
if(arguments.length >= 2) {
return juicer.to_html.apply(juicer, args);
}
};
var __escapehtml = {
escapehash: {
'<': '<',
'>': '>',
'&': '&',
'"': '"',
"'": ''',
'/': '/'
},
escapereplace: function(k) {
return __escapehtml.escapehash[k];
},
escaping: function(str) {
return typeof(str) !== 'string' ? str : str.replace(/[&<>"]/igm, this.escapereplace);
},
detection: function(data) {
return typeof(data) === 'undefined' ? '' : data;
}
};
var __throw = function(error) {
if(typeof(console) !== 'undefined') {
if(console.warn) {
console.warn(error);
return;
}
if(console.log) {
console.log(error);
return;
}
}
throw(error);
};
var __creator = function(o, proto) {
o = o !== Object(o) ? {} : o;
if(o.__proto__) {
o.__proto__ = proto;
return o;
}
var empty = function() {};
var n = Object.create ?
Object.create(proto) :
new(empty.prototype = proto, empty);
for(var i in o) {
if(o.hasOwnProperty(i)) {
n[i] = o[i];
}
}
return n;
};
var annotate = function(fn) {
var FN_ARGS = /^function\s*[^\(]*\(\s*([^\)]*)\)/m;
var FN_ARG_SPLIT = /,/;
var FN_ARG = /^\s*(_?)(\S+?)\1\s*$/;
var FN_BODY = /^function[^{]+{([\s\S]*)}/m;
var STRIP_COMMENTS = /((\/\/.*$)|(\/\*[\s\S]*?\*\/))/mg;
var args = [],
fnText,
fnBody,
argDecl;
if (typeof fn === 'function') {
if (fn.length) {
fnText = fn.toString();
}
} else if(typeof fn === 'string') {
fnText = fn;
}
fnText = fnText.replace(STRIP_COMMENTS, '');
fnText = fnText.trim();
argDecl = fnText.match(FN_ARGS);
fnBody = fnText.match(FN_BODY)[1].trim();
for(var i = 0; i < argDecl[1].split(FN_ARG_SPLIT).length; i++) {
var arg = argDecl[1].split(FN_ARG_SPLIT)[i];
arg.replace(FN_ARG, function(all, underscore, name) {
args.push(name);
});
}
return [args, fnBody];
};
juicer.__cache = {};
juicer.version = '0.6.8-stable';
juicer.settings = {};
juicer.tags = {
operationOpen: '{@',
operationClose: '}',
interpolateOpen: '\\${',
interpolateClose: '}',
noneencodeOpen: '\\$\\${',
noneencodeClose: '}',
commentOpen: '\\{#',
commentClose: '\\}'
};
juicer.options = {
cache: true,
strip: true,
errorhandling: true,
detection: true,
_method: __creator({
__escapehtml: __escapehtml,
__throw: __throw,
__juicer: juicer
}, {})
};
juicer.tagInit = function() {
var forstart = juicer.tags.operationOpen + 'each\\s*([^}]*?)\\s*as\\s*(\\w*?)\\s*(,\\s*\\w*?)?' + juicer.tags.operationClose;
var forend = juicer.tags.operationOpen + '\\/each' + juicer.tags.operationClose;
var ifstart = juicer.tags.operationOpen + 'if\\s*([^}]*?)' + juicer.tags.operationClose;
var ifend = juicer.tags.operationOpen + '\\/if' + juicer.tags.operationClose;
var elsestart = juicer.tags.operationOpen + 'else' + juicer.tags.operationClose;
var elseifstart = juicer.tags.operationOpen + 'else if\\s*([^}]*?)' + juicer.tags.operationClose;
var interpolate = juicer.tags.interpolateOpen + '([\\s\\S]+?)' + juicer.tags.interpolateClose;
var noneencode = juicer.tags.noneencodeOpen + '([\\s\\S]+?)' + juicer.tags.noneencodeClose;
var inlinecomment = juicer.tags.commentOpen + '[^}]*?' + juicer.tags.commentClose;
var rangestart = juicer.tags.operationOpen + 'each\\s*(\\w*?)\\s*in\\s*range\\(([^}]+?)\\s*,\\s*([^}]+?)\\)' + juicer.tags.operationClose;
var include = juicer.tags.operationOpen + 'include\\s*([^}]*?)\\s*,\\s*([^}]*?)' + juicer.tags.operationClose;
var helperRegisterStart = juicer.tags.operationOpen + 'helper\\s*([^}]*?)\\s*' + juicer.tags.operationClose;
var helperRegisterBody = '([\\s\\S]*?)';
var helperRegisterEnd = juicer.tags.operationOpen + '\\/helper' + juicer.tags.operationClose;
juicer.settings.forstart = new RegExp(forstart, 'igm');
juicer.settings.forend = new RegExp(forend, 'igm');
juicer.settings.ifstart = new RegExp(ifstart, 'igm');
juicer.settings.ifend = new RegExp(ifend, 'igm');
juicer.settings.elsestart = new RegExp(elsestart, 'igm');
juicer.settings.elseifstart = new RegExp(elseifstart, 'igm');
juicer.settings.interpolate = new RegExp(interpolate, 'igm');
juicer.settings.noneencode = new RegExp(noneencode, 'igm');
juicer.settings.inlinecomment = new RegExp(inlinecomment, 'igm');
juicer.settings.rangestart = new RegExp(rangestart, 'igm');
juicer.settings.include = new RegExp(include, 'igm');
juicer.settings.helperRegister = new RegExp(helperRegisterStart + helperRegisterBody + helperRegisterEnd, 'igm');
};
juicer.tagInit();
// Using this method to set the options by given conf-name and conf-value,
// you can also provide more than one key-value pair wrapped by an object.
// this interface also used to custom the template tag delimater, for this
// situation, the conf-name must begin with tag::, for example: juicer.set
// ('tag::operationOpen', '{@').
juicer.set = function(conf, value) {
var that = this;
var escapePattern = function(v) {
return v.replace(/[\$\(\)\[\]\+\^\{\}\?\*\|\.]/igm, function($) {
return '\\' + $;
});
};
var set = function(conf, value) {
var tag = conf.match(/^tag::(.*)$/i);
if(tag) {
that.tags[tag[1]] = escapePattern(value);
that.tagInit();
return;
}
that.options[conf] = value;
};
if(arguments.length === 2) {
set(conf, value);
return;
}
if(conf === Object(conf)) {
for(var i in conf) {
if(conf.hasOwnProperty(i)) {
set(i, conf[i]);
}
}
}
};
// Before you're using custom functions in your template like ${name | fnName},
// you need to register this fn by juicer.register('fnName', fn).
juicer.register = function(fname, fn) {
var _method = this.options._method;
if(_method.hasOwnProperty(fname)) {
return false;
}
return _method[fname] = fn;
};
// remove the registered function in the memory by the provided function name.
// for example: juicer.unregister('fnName').
juicer.unregister = function(fname) {
var _method = this.options._method;
if(_method.hasOwnProperty(fname)) {
return delete _method[fname];
}
};
juicer.template = function(options) {
var that = this;
this.options = options;
this.__interpolate = function(_name, _escape, options) {
var _define = _name.split('|'), _fn = _define[0] || '', _cluster;
if(_define.length > 1) {
_name = _define.shift();
_cluster = _define.shift().split(',');
_fn = '_method.' + _cluster.shift() + '.call({}, ' + [_name].concat(_cluster) + ')';
}
return '<%= ' + (_escape ? '_method.__escapehtml.escaping' : '') + '(' +
(!options || options.detection !== false ? '_method.__escapehtml.detection' : '') + '(' +
_fn +
')' +
')' +
' %>';
};
this.__removeShell = function(tpl, options) {
var _counter = 0;
tpl = tpl
// inline helper register
.replace(juicer.settings.helperRegister, function($, helperName, fnText) {
var anno = annotate(fnText);
var fnArgs = anno[0];
var fnBody = anno[1];
var fn = new Function(fnArgs.join(','), fnBody);
juicer.register(helperName, fn);
return $;
})
// for expression
.replace(juicer.settings.forstart, function($, _name, alias, key) {
var alias = alias || 'value', key = key && key.substr(1);
var _iterate = 'i' + _counter++;
return '<% ~function() {' +
'for(var ' + _iterate + ' in ' + _name + ') {' +
'if(' + _name + '.hasOwnProperty(' + _iterate + ')) {' +
'var ' + alias + '=' + _name + '[' + _iterate + '];' +
(key ? ('var ' + key + '=' + _iterate + ';') : '') +
' %>';
})
.replace(juicer.settings.forend, '<% }}}(); %>')
// if expression
.replace(juicer.settings.ifstart, function($, condition) {
return '<% if(' + condition + ') { %>';
})
.replace(juicer.settings.ifend, '<% } %>')
// else expression
.replace(juicer.settings.elsestart, function($) {
return '<% } else { %>';
})
// else if expression
.replace(juicer.settings.elseifstart, function($, condition) {
return '<% } else if(' + condition + ') { %>';
})
// interpolate without escape
.replace(juicer.settings.noneencode, function($, _name) {
return that.__interpolate(_name, false, options);
})
// interpolate with escape
.replace(juicer.settings.interpolate, function($, _name) {
return that.__interpolate(_name, true, options);
})
// clean up comments
.replace(juicer.settings.inlinecomment, '')
// range expression
.replace(juicer.settings.rangestart, function($, _name, start, end) {
var _iterate = 'j' + _counter++;
return '<% ~function() {' +
'for(var ' + _iterate + '=' + start + ';' + _iterate + '<' + end + ';' + _iterate + '++) {{' +
'var ' + _name + '=' + _iterate + ';' +
' %>';
})
// include sub-template
.replace(juicer.settings.include, function($, tpl, data) {
// compatible for node.js
if(tpl.match(/^file\:\/\//igm)) return $;
return '<%= _method.__juicer(' + tpl + ', ' + data + '); %>';
});
// exception handling
if(!options || options.errorhandling !== false) {
tpl = '<% try { %>' + tpl;
tpl += '<% } catch(e) {_method.__throw("Juicer Render Exception: "+e.message);} %>';
}
return tpl;
};
this.__toNative = function(tpl, options) {
return this.__convert(tpl, !options || options.strip);
};
this.__lexicalAnalyze = function(tpl) {
var buffer = [];
var method = [];
var prefix = '';
var reserved = [
'if', 'each', '_', '_method', 'console',
'break', 'case', 'catch', 'continue', 'debugger', 'default', 'delete', 'do',
'finally', 'for', 'function', 'in', 'instanceof', 'new', 'return', 'switch',
'this', 'throw', 'try', 'typeof', 'var', 'void', 'while', 'with', 'null', 'typeof',
'class', 'enum', 'export', 'extends', 'import', 'super', 'implements', 'interface',
'let', 'package', 'private', 'protected', 'public', 'static', 'yield', 'const', 'arguments',
'true', 'false', 'undefined', 'NaN'
];
var indexOf = function(array, item) {
if (Array.prototype.indexOf && array.indexOf === Array.prototype.indexOf) {
return array.indexOf(item);
}
for(var i=0; i < array.length; i++) {
if(array[i] === item) return i;
}
return -1;
};
var variableAnalyze = function($, statement) {
statement = statement.match(/\w+/igm)[0];
if(indexOf(buffer, statement) === -1 && indexOf(reserved, statement) === -1 && indexOf(method, statement) === -1) {
// avoid re-declare native function, if not do this, template
// `{@if encodeURIComponent(name)}` could be throw undefined.
if(typeof(window) !== 'undefined' && typeof(window[statement]) === 'function' && window[statement].toString().match(/^\s*?function \w+\(\) \{\s*?\[native code\]\s*?\}\s*?$/i)) {
return $;
}
// compatible for node.js
if(typeof(global) !== 'undefined' && typeof(global[statement]) === 'function' && global[statement].toString().match(/^\s*?function \w+\(\) \{\s*?\[native code\]\s*?\}\s*?$/i)) {
return $;
}
// avoid re-declare registered function, if not do this, template
// `{@if registered_func(name)}` could be throw undefined.
if(typeof(juicer.options._method[statement]) === 'function' || juicer.options._method.hasOwnProperty(statement)) {
method.push(statement);
return $;
}
buffer.push(statement); // fuck ie
}
return $;
};
tpl.replace(juicer.settings.forstart, variableAnalyze).
replace(juicer.settings.interpolate, variableAnalyze).
replace(juicer.settings.ifstart, variableAnalyze).
replace(juicer.settings.elseifstart, variableAnalyze).
replace(juicer.settings.include, variableAnalyze).
replace(/[\+\-\*\/%!\?\|\^&~<>=,\(\)\[\]]\s*([A-Za-z_]+)/igm, variableAnalyze);
for(var i = 0;i < buffer.length; i++) {
prefix += 'var ' + buffer[i] + '=_.' + buffer[i] + ';';
}
for(var i = 0;i < method.length; i++) {
prefix += 'var ' + method[i] + '=_method.' + method[i] + ';';
}
return '<% ' + prefix + ' %>';
};
this.__convert=function(tpl, strip) {
var buffer = [].join('');
buffer += "'use strict';"; // use strict mode
buffer += "var _=_||{};";
buffer += "var _out='';_out+='";
if(strip !== false) {
buffer += tpl
.replace(/\\/g, "\\\\")
.replace(/[\r\t\n]/g, " ")
.replace(/'(?=[^%]*%>)/g, "\t")
.split("'").join("\\'")
.split("\t").join("'")
.replace(/<%=(.+?)%>/g, "';_out+=$1;_out+='")
.split("<%").join("';")
.split("%>").join("_out+='")+
"';return _out;";
return buffer;
}
buffer += tpl
.replace(/\\/g, "\\\\")
.replace(/[\r]/g, "\\r")
.replace(/[\t]/g, "\\t")
.replace(/[\n]/g, "\\n")
.replace(/'(?=[^%]*%>)/g, "\t")
.split("'").join("\\'")
.split("\t").join("'")
.replace(/<%=(.+?)%>/g, "';_out+=$1;_out+='")
.split("<%").join("';")
.split("%>").join("_out+='")+
"';return _out.replace(/[\\r\\n]\\s+[\\r\\n]/g, '\\r\\n');";
return buffer;
};
this.parse = function(tpl, options) {
var _that = this;
if(!options || options.loose !== false) {
tpl = this.__lexicalAnalyze(tpl) + tpl;
}
tpl = this.__removeShell(tpl, options);
tpl = this.__toNative(tpl, options);
this._render = new Function('_, _method', tpl);
this.render = function(_, _method) {
if(!_method || _method !== that.options._method) {
_method = __creator(_method, that.options._method);
}
return _that._render.call(this, _, _method);
};
return this;
};
};
juicer.compile = function(tpl, options) {
if(!options || options !== this.options) {
options = __creator(options, this.options);
}
try {
var engine = this.__cache[tpl] ?
this.__cache[tpl] :
new this.template(this.options).parse(tpl, options);
if(!options || options.cache !== false) {
this.__cache[tpl] = engine;
}
return engine;
} catch(e) {
__throw('Juicer Compile Exception: ' + e.message);
return {
render: function() {} // noop
};
}
};
juicer.to_html = function(tpl, data, options) {
if(!options || options !== this.options) {
options = __creator(options, this.options);
}
return this.compile(tpl, options).render(data, options._method);
};
typeof(module) !== 'undefined' && module.exports ? module.exports = juicer : this.juicer = juicer;
})();
</script>
<script type="text/x-contemplate" id="contemplateextTemplate">
<# #>
<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($list as $item) #><li class='item'><# $item #></li><# %endfor() #></ul></div></script>
sharedVariables = {
header: "Header",
header2: "Header 2",
header3: "Header 3",
header4: "Header 4",
header5: "Header 5",
header6: "Header 6",
list: ['1', '2', '3', '4', '5', '6', '7', '8', '9', '10']
};
var element = document.getElementById('target');
Ready to run.
Test | Ops/sec | |
---|---|---|
mustache.js |
| ready |
underscore.js |
| ready |
handlebar.js |
| ready |
Hogan.js |
| ready |
Resig Micro Templates (modified) |
| ready |
Resig Micro Templates (No "with" block) |
| ready |
underscore.js (_.each) |
| ready |
Mustache (compiled) |
| ready |
HandleHash |
| ready |
underscore each cached |
| ready |
underscore for cached |
| ready |
doT.js |
| ready |
doT.js 2 |
| ready |
Whiskers.js |
| ready |
React |
| ready |
Markup.js |
| ready |
juicer(compiled) |
| ready |
juicer |
| ready |
contemplate |
| ready |
You can edit these tests or add more tests to this page by appending /edit to the URL.