strings vs array join vs doc fragment (v4)

Revision 4 of this benchmark created on


Description

Testing performance of building a string and appending as innerHTML vs creating a documentFragmente and appending it as DOM.

Preparation HTML

<style>
#list li{float:left}
</style>
<ul id="list">
</ul>

Setup

// doT.js
    // 2011, Laura Doktorova
    // https://github.com/olado/doT
    //
    // doT is a custom blend of templating functions from jQote2.js
    // (jQuery plugin) by aefxx (http://aefxx.com/jquery-plugins/jqote2/)
    // and underscore.js (http://documentcloud.github.com/underscore/)
    // plus extensions.
    //
    // Licensed under the MIT license.
    //
    (function() {
        var doT = { version : '0.1.6' };
    
        if (typeof module !== 'undefined' && module.exports) {
                module.exports = doT;
        } else {
                this.doT = doT;
        }
    
        doT.templateSettings = {
                evaluate:    /\{\{([\s\S]+?)\}\}/g,
                interpolate: /\{\{=([\s\S]+?)\}\}/g,
                encode:      /\{\{!([\s\S]+?)\}\}/g,
                use:         /\{\{#([\s\S]+?)\}\}/g, //compile time evaluation
                define:      /\{\{##\s*([\w\.$]+)\s*(\:|=)([\s\S]+?)#\}\}/g, //compile time defs
                varname: 'it',
                strip : true,
                append: true
        };
    
        function resolveDefs(c, block, def) {
                return ((typeof block === 'string') ? block : block.toString())
                .replace(c.define, function (match, code, assign, value) {
                        if (code.indexOf('def.') === 0) {
                                code = code.substring(4);
                        }
                        if (!(code in def)) {
                                if (assign === ':') {
                                        def[code]= value;
                                } else {
                                        eval("def[code]=" + value);
                                }
                        }
                        return '';
                })
                .replace(c.use, function(match, code) {
                        var v = eval(code);
                        return v ? resolveDefs(c, v, def) : v;
                });
        }
    
        doT.template = function(tmpl, c, def) {
                c = c || doT.templateSettings;
                var cstart = c.append ? "'+(" : "';out+=(", // optimal choice depends on platform/size of templates
                    cend   = c.append ? ")+'" : ");out+='";
                var str = (c.use || c.define) ? resolveDefs(c, tmpl, def || {}) : tmpl;
    
                str = ("var out='" +
                        ((c.strip) ? str.replace(/\s*<!\[CDATA\[\s*|\s*\]\]>\s*|[\r\n\t]|(\/\*[\s\S]*?\*\/)/g, ''): str)
                        .replace(/\\/g, '\\\\')
                        .replace(/'/g, "\\'")
                        .replace(c.interpolate, function(match, code) {
                                return cstart + code.replace(/\\'/g, "'").replace(/\\\\/g,"\\").replace(/[\r\t\n]/g, ' ') + cend;
                        })
                        .replace(c.encode, function(match, code) {
                                return cstart + code.replace(/\\'/g, "'").replace(/\\\\/g, "\\").replace(/[\r\t\n]/g, ' ') + ").toString().replace(/&(?!\\w+;)/g, '&#38;').split('<').join('&#60;').split('>').join('&#62;').split('" + '"' + "').join('&#34;').split(" + '"' + "'" + '"' + ").join('&#39;').split('/').join('&#47;'" + cend;
                        })
                        .replace(c.evaluate, function(match, code) {
                                return "';" + code.replace(/\\'/g, "'").replace(/\\\\/g,"\\").replace(/[\r\t\n]/g, ' ') + "out+='";
                        })
                        + "';return out;")
                        .replace(/\n/g, '\\n')
                        .replace(/\t/g, '\\t')
                        .replace(/\r/g, '\\r')
                        .split("out+='';").join('')
                        .split("var out='';out+=").join('var out=');
    
                try {
                        return new Function(c.varname, str);
                } catch (e) {
                        if (typeof console !== 'undefined') console.log("Could not create a template function: " + str);
                        throw e;
                }
        };
    }());
    var Template = function(id){
                   this.r = doT.template(document.getElementById('must').innerHTML);
                   this.o = [];
                   this.add = function(data){
                       this.o.push(this.r(data));
                   }
                   this.getOutput = function(){
                       return this.o.join('');
                   }
               };
    var d = {first: 'lorem', second: 'ipsum'};

Teardown


    document.getElementById('list').innerHTML = '';
  

Test runner

Ready to run.

Testing in
TestOps/sec
Strings
var str = '';
for(var i = 0 ; i < 100 ; i++){
str+='<li>'+d.first + ' ' + d.second+'</li>';
}
document.getElementById('list').innerHTML = str;
ready
Array (using push)
var arr = [];
for(var i = 0 ; i < 100 ; i++){
arr.push('<li>'+d.first + ' ' + d.second+'</li>');
}
document.getElementById('list').innerHTML = arr.join('');
ready
Doc Fragment
var doc = document.createDocumentFragment();
for(var i = 0 ; i < 100 ; i++){
var li = document.createElement('li');
li.innerHTML = d.first + ' ' + d.second;
doc.appendChild(li);
}
document.getElementById('list').appendChild(doc);
ready
Array (using index)
var arr = [];
for(var i = 0 ; i < 100 ; i++){
arr[i] = '<li>'+d.first + ' ' + d.second+'</li>';
}
document.getElementById('list').innerHTML = arr.join('');
ready
Array join then string concat
var arr = [];
for(var i = 0 ; i < 100 ; i++){
arr[i] = d.first + ' ' + d.second;
}
document.getElementById('list').innerHTML = '<li>' + arr.join('</li><li>') + '</li>';
ready

Revisions

You can edit these tests or add more tests to this page by appending /edit to the URL.