This site's run a test between the 3 different methods and it seems .html is the fastest, followed by .append. followed by .innerHTML. Can someone explain to me the reasons why?
Here's the site which does the comparison among the three methods.
I have read this this SO question which is related but I don't really understand the given answer, and the question didn't really elaborate much regarding .innerHtml.
I don't understand the following part:
A temporary element is created, let's call it x. x's innerHTML is set to the string of HTML that you've passed. Then jQuery will transfer each of the produced nodes (that is, x's childNodes) over to a newly created document fragment, which it will then cache for next time. It will then return the fragment's childNodes as a fresh DOM collection.
Note that it's actually a lot more complicated than that, as jQuery does a bunch of cross-browser checks and various other optimisations. E.g. if you pass just <div></div> to jQuery(), jQuery will take a shortcut and simply do document.createElement('div').
Can someone simplify this?
All three are slow to me. Modifying the dom on each iteration is slow.
http://jsperf.com/jquery-append-vs-html-list-performance/24
I just added a new test in there:
var html = [];
for (var i = 0; i < len; i++) {
html.push('<div>Test ' + i + '</div>');
}
document.getElementById('list').innerHTML = html.join('');
This is much faster again. :)
My method in Firefox does 26k Ops/sec vs 1,000, 10,000, and 13
That benchmark is worthless. innerHTML is always faster than DOM manipulation.
jQuery seems faster because it prepares a string with all the HTML first while the others do one operation each iteration. Also note that jQuery.html() uses innerHTML whenever it can.
jQuery from benchmark
var html = '';
for (var i = 0; i < len; i++) {
html += '<div>Test ' + i + '</div>';
}
$('#list').html(html);
innerHTML from benchmark
var list = document.getElementById('list');
for (var i = 0; i < len; i++) {
list.innerHTML = list.innerHTML + '<div>Test ' + i + '</div>';
}
The test for innerHTML would be a lot faster if it was written like:
var list = document.getElementById('list');
var html = '';
for (var i = 0; i < len; i++) {
html += '<div>Test ' + i + '</div>';
}
list.innerHTML = html;
http://jsben.ch/#/yDvKH
How can .html be faster than .innerHTML when the .html is using .innerHTML with a lot of extra code? Here .html implementation in jQuery (taken directly from jQuery file).
html: function( value ) {
return jQuery.access( this, function( value ) {
var elem = this[0] || {},
i = 0,
l = this.length;
if ( value === undefined ) {
return elem.nodeType === 1 ?
elem.innerHTML.replace( rinlinejQuery, "" ) :
undefined;
}
// See if we can take a shortcut and just use innerHTML
if ( typeof value === "string" && !rnoInnerhtml.test( value ) &&
( jQuery.support.htmlSerialize || !rnoshimcache.test( value ) ) &&
( jQuery.support.leadingWhitespace || !rleadingWhitespace.test( value ) ) &&
!wrapMap[ ( rtagName.exec( value ) || ["", ""] )[1].toLowerCase() ] ) {
value = value.replace( rxhtmlTag, "<$1></$2>" );
try {
for (; i < l; i++ ) {
// Remove element nodes and prevent memory leaks
elem = this[i] || {};
if ( elem.nodeType === 1 ) {
jQuery.cleanData( getAll( elem, false ) );
elem.innerHTML = value;
}
}
elem = 0;
// If using innerHTML throws an exception, use the fallback method
} catch(e) {}
}
if ( elem ) {
this.empty().append( value );
}
}, null, value, arguments.length );
}
As Bart said, innerHTML is always faster than DOM manipulation.
I was testing hyperHTML, so I thought I share my results. I didn't actually run my benchmarks in CodePen originally, and there is an interesting difference in that the jQuery times are much closer to innerHTML running in CodePen.
Chrome:
createFragment 312.80 ms
hyperHTML 253.10 ms
innerHTML 62.70 ms
$.append 183.40 ms
Chrome (extensions off):
createFragment 225.10 ms
hyperHTML 139.80 ms
innerHTML 47.80 ms
$.append 170.90 ms
Firefox:
createFragment 141 ms
hyperHTML 84 ms
innerHTML 25 ms
$.append 90 ms
Edge:
createFragment 422.50 ms
hyperHTML 184.60 ms
innerHTML 44.00 ms
$.append 1629.69 ms
IE11:
createFragment 1180.29 ms
hyperHTML 13315.59 ms //slow fallbacks, IE sucks
innerHTML 125.70 ms
$.append 2382.49 ms
I think it is all pretty simple. JavaScript is not as fast as the browser at parsing and creating elements, because the browser is machine specific compiled code. You can't do better than just handing over the HTML and letting the browser do the work without interruption.
It is possible that some of the performance differences are due to XSS checking, which would seem prudent.
function runbench(){
var data = [];
for (var i = 0; i < 10001; i++) {
data.push("<span>" + i + "</span>");
}
var perf=[];
var t0 = performance.now();
var c = document.createDocumentFragment();
for (var i = 0; i < 10001; i++) {
var e = document.createElement("span");
e.innerHTML = data[i];
c.appendChild(e);
}
document.querySelector('#createFragment').appendChild(c);
document.querySelector('#createFragment').classList='done';
var t1 = performance.now();
perf.push(t1-t0);
var t0 = performance.now();
document.querySelector('#innerHTML').innerHTML = data.join('');
document.querySelector('#innerHTML').classList='done';
var t1 = performance.now();
perf.push(t1-t0);
var t0 = performance.now();
$('#jqhtml').html(data.join(''));
document.querySelector('#jqhtml').classList='done';
var t1 = performance.now();
perf.push(t1-t0);
var t0 = performance.now();
$('#jqappend').append(data.join(''));
document.querySelector('#jqappend').classList='done';
var t1 = performance.now();
perf.push(t1-t0);
var t0 = performance.now();
hyperHTML.bind(document.querySelector('#hyperHTML'))
`${data.map(function (item) {
return "<span>" + item + "</span>";
})}`;
document.querySelector('#hyperHTML').classList='done';
var t1 = performance.now();
perf.push(t1-t0);
var stats = [];
stats.push("<table>")
stats.push("<tr><td>createFrag: </td><td>" + perf[0].toFixed(2) + "</td></tr>");
stats.push("<tr><td>innerHTML: </td><td>" + perf[1].toFixed(2) + "</td></tr>");
stats.push("<tr><td>$.html: </td><td>" + perf[2] .toFixed(2) + "</td></tr>");
stats.push("<tr><td>$.append: </td><td>" + perf[3] .toFixed(2) + "</td></tr>");
stats.push("<tr><td>hyperHTML: </td><td>" + perf[4].toFixed(2) + "</td></tr>");
stats.push("</table>");
$('#performance').html(stats.join(''));
document.querySelector('#performance').classList='done';
}
https://codepen.io/jwhooper/pen/GzKwMV
I think the innerHTML is faster with suggesstion #Brat.
And on creating loop and appending string should be good on using variable first.
It is make your performance more good.
good code:
var html = '';
for (var i = 0; i < len; i++) {
html += '<div>Test ' + i + '</div>';
};
$('#list').append(html);
not efficient code:
for (var i = 0; i < len; i++) {
var html = '<div>Test ' + i + '</div>';
$('#list').append(html);
}
for example: http://jsben.ch/#/yDvKH
I also had a problem with big table redraw (about 10x100 size). It takes about 300ms to redraw whole table.
The reason was not in the jQuery.append() and not in dom.innerHTML, but in appending each element each time.
The fastest way is to concatenate all elements html code and then append it to DOM.
Like this:
function redrawMyTable( myData )
{
var innerHTML = '';
for ( var i = 0; i < myData.length; i++ )
{
innerHTML += createRowFromData( myData[i] );
}
myTableTbody.innerHTML = innerHTML;
}
function createRowFromData( rowData )
{
var rowHTML = '';
for ( var i = 0; i < rowData.length; i++ )
{
rowHTML += createCellFromData( rowData[i] );
}
return rowHTML;
}
function createCellFromData( cellData )
{
//Do everything you need, and return HTMl code as a string
return cellHTML;
}
Now it takes only 20-30 ms (against 300ms :))
6 years later
Point is - don't manipulate the live DOM. Do it outside. Today, it doesn't matter where. You can use a HTML String, a DocumentFragment (which excludes Internet Explorer) or create a new Element but don't add it to the DOM, fill it as you need and THEN add it.
On Chrome and Firefox my observation is that it's all the same run time, give or take a few percent.
Building a long HTML String in chunks that are stored in an array and then join('')-ed is also not necessary any more. Years ago, I measured big time differences. Not today. Point one: there's no recognizable time difference (on Chrome and FF), and point two: the time isn't lost at this point, but in rendering.
my code innerHTML vs fragment
fragment use
running time : 1300~1500ms
innerHTML use
running time : 1800~2000ms
`
const data = [];
for(let i = 0; i < 1000001;i++){
data.push(i);
}
function useInnerHtml(result_wrap){
let text = ''
for(const item of data){
text += '<div>' + item + '</div>';
}
result_wrap.innerHTML = text;
}
function useFragment(result_wrap){
const fragment= new DocumentFragment(); // or document.createDocumentFragment();
for(const item of data){
const div = document.createElement('div');
div.textContent = item;
fragment.appendChild(div);
}
result_wrap.appendChild(fragment);
}
function createData(obj){
let startTime = new Date().getTime();
const targetParentNode = obj.parentNode;
const result_wrap = targetParentNode.querySelector('.result-wrap');
if(result_wrap.hasChildNodes() == false){
if(result_wrap.className.includes('inner-html')){
useInnerHtml(result_wrap);
}else if(result_wrap.className.includes('fragment')){
useFragment(result_wrap, targetParentNode);
}else{
alert('');
}
}else{
alert('click remove button');
}
let endTime = new Date().getTime();
let time = (endTime - startTime);
targetParentNode.querySelector('.running-time').textContent = 'running time : ' + time + 'ms'
}
function removeContent(){
[...document.querySelectorAll('.result-wrap')].map(e=>e.replaceChildren());
}
`
https://codepen.io/joohyoungkim19940805/pen/BaJQeGW
Related
I'm making a firefox addon that appends words with other words in html text. This code works, but when iterating through an enormous dictionary, I get an unresponsive script error.
What is the best way to improve the speed of this loop?
Splitting up the dictionary into smaller objects? Or setting a timeout function?
var brands = {"manykeys" : "manyvalues"};
function replaceWord(){
for (var key in brands){
htmlreplace(key, key + " (" + brands[key] + ")");
}
}
function htmlreplace(a, b, element) {
if (!element) element = document.body;
var nodes = element.childNodes;
for (var n=0; n<nodes.length; n++) {
if (nodes[n].nodeType == Node.TEXT_NODE) {
var r = new RegExp(a, 'g');
nodes[n].textContent = nodes[n].textContent.replace(r, b);
} else {
htmlreplace(a, b, nodes[n]);
}
}
}
replaceWord();
There are some considerations to take. It depends a lot on what you can change or not. One of the bigger improvements you can do is using array instead of key/value object.
var brands = [
['manykeys0000','manyvalues0000'],
['manykeys0001','manyvalues0001'],
['manykeys0002','manyvalues0002'],
['manykeys0003','manyvalues0003'],
['manykeys0004', ...
];
function replaceWord(){
var i, n = brands.length;
for (i = 0; i < n; ++i) {
htmlreplace(brands[i][0], brands[i][0] + " (" + brands[i][1] + ")");
}
}
A couple of other changes should also give a tiny improvement:
1.) Move nodes.length outside the loop.
2.) If suitable pass document.body from replaceWord()
var body = document.body;
...
htmlreplace(brands[i][0], brands[i][0] + " (" + brands[i][2] + ")", body);
function htmlreplace(a, b, element) {
var nodes = element.childNodes, len = nodes.length;
for (var n=0; n < len; ++n) {
Combined quick benchmark on Chrome and Firefox gave a 30-40% increase in speed.
Other edits to test out:
Move var r = new RegExp(a, 'g'); to replaceWord(), and pass it as first argument to htmlreplace() instead of a.
function replaceWord(){
var i, n = brands.length;
for (i = 0; i < n; ++i) {
var r = new RegExp(brands[i][0], 'g');
htmlreplace(r, brands[i].join(' (') + ')', elem);
}
}
If you play with timeout this article might be of interest. It uses
window.postMessage();
to implement a custom setZeroTimeout() but not sure how it will impact your case.
Beside JSPerf etc. use profiling tools in browser, for example in Chrome, which might be better suited for what you do in your code.
I haven't tried, but doing this might work:
function replaceWord(){
for (var key in brands){
(function(key) {
setTimeout(function() {
htmlreplace(key, key + " (" + brands[key] + ")");
}, 0);
})(key);
}
}
The idea is that you postpone the replacing for when the browser has time for it, instead of doing it monolithically and making the browser freeze while it's thinking.
The bottleneck in your code is not the dictionary size, unless it is really big, but the DOM traversal.
Get the text nodes once and then work with them.
var textnodes = $x("//text()", document.body)
function $x(p, context) {
if (!context) context = document;
var i, arr = [], xpr = document.evaluate(p, context, null, XPathResult.UNORDERED_NODE_SNAPSHOT_TYPE, null);
for (i = 0; item = xpr.snapshotItem(i); i++) arr.push(item);
return arr;
}
You should see a considerable speed improvement.
So I'm arriving at last stops of my Apps journey (there'll be several others =).
The code bellow is a function that aims to do this: iterates through the paragraphs of one google text document and, when it finds in text some sinal, some paragraph (such as "Introduction", "Part 1 - Background" or "Part 2 - Biography") whose content is igual a theses_type, it inserts all theses_type paragraphs into the first document, after that sinal or theses_type keyword.
So, I'm trying to do this with the function "importTheses" (thanks #Serge insas and others for previous help!). But I'm having trouble after the line for( var k = 0; k < thesesParagraphs-1; ++k ). Even when I got through the log the boolean True, I can't get the paragraphs inserted. I also can't get the log of this line: Logger.log("thesesDoc.getText() = " + thesesElement.getText() );. So, any help or hint will be very appreciated.
function importTheses(targetDocId, thesesId, thesesType) { // just a name, I used it to analyse docs
var targetDoc = DocumentApp.openById(targetDocId);
var targetDocParagraphs = targetDoc.getParagraphs();
var targetDocElements = targetDoc.getNumChildren();
var thesesDoc = DocumentApp.openById(thesesId);
var thesesParagraphs = thesesDoc.getParagraphs();
var thesesElements = thesesDoc.getNumChildren();
Logger.log("targetDocId = " + targetDocId);
Logger.log("thesesId = " + thesesId);
Logger.log("thesesType = " + thesesType);
var elTargetDoc=[];
var elTheses=[];
for (var j = 0; j < targetDocElements; ++j ) {
var targetDocElement = targetDoc.getChild(j);
Logger.log("targetDoc.getChild(j) = " + targetDocElement);// to see targetDoc's content
elTargetDoc[j]=targetDocElement.getText();
if(elTargetDoc[j] == thesesType){
Logger.log("elTargetDoc[j]== " + elTargetDoc[j]);
Logger.log("thesesType " + thesesType);
Logger.log("if(elTargetDoc[j]== thesesType)" + (elTargetDoc[j]== thesesType) );
for( var k = 0; k < thesesParagraphs-1; ++k ) {
var thesesElement = thesesDoc.getChild(k);
Logger.log("thesesDoc.getChild(k) " + thesesDoc.getChild(k));
Logger.log("thesesDoc.getText() = " + thesesElement.getText() );
elTheses[k] = thesesElement.getText();
targetDoc.insertParagraph(j, elTheses[k]);
}
}
}
}
for( var k = 0; k < thesesParagraphs-1; ++k ) { .. }
In this line of code, you're intention is to loop using k, over integer values starting at 0. The loop should run at least once, as long as thesesParagraphs is 2 or more... and is a number.
That second condition is your problem. Earlier in the function, you had this:
var thesesParagraphs = thesesDoc.getParagraphs();
... so thesesParagraphs is an Array of Paragraph objects, not a number. You are probably interested in the count of paragraphs:
for( var k = 0; k < thesesParagraphs.length-1; ++k ) { .. }
^^^^^^^
Or perhaps thesesElements was what you intended to use to bound your loop, since it's a number.
I am wondering how to make the loop below more efficient. On my page it has more iterations than 100, that is why, as you can imagine it is troublesome.
for (var i = 0; i < 1000; i += 1) {
var el = document.createElement('div');
el.appendChild(document.createTextNode('Node ' + (i + 1)));
document.getElementById('nodeHolder').appendChild(el);
}
Thanx for help in advance.
All I can really suggest is getting a reference to the nodeHolder element outside of the for:
var nodeHolder = document.getElementById('nodeHolder');
for (var i = 0; i < 1000; i += 1) {
var el = document.createElement('div');
el.appendChild(document.createTextNode('Node ' + (i + 1)));
nodeHolder.appendChild(el);
}
Or perhaps using a document fragment to hold the interim changes/appends and then add that to to the nodeHolder after the loop:
var fragment = document.createDocumentFragment();
for (var i = 0; i < 1000; i += 1) {
var el = document.createElement('div');
el.appendChild(document.createTextNode('Node ' + (i + 1)));
fragment.appendChild(el);
}
document.getElementById('nodeHolder').appendChild(fragment.cloneNode(true));
JS Fiddle showing both approaches (with timing if the console is available).
jQuery way...
var d = '';
for(var i=0;i<1000;i++) d += '<div>Node ' + (i + 1) + '</div>';
$('#nodeHolder').append(d);
Or javascript inside html...
<div id="nodeHolder">
<script>(function() { for(var i=0;i<1000;i++) document.write('<div>Node ' + (i + 1) + '</div>'); })();</script>
</div>
Maybe: You could generate a HTML String in the for loop like:
<div>Node 1</div><div>Node 2</div>
and then set the .innerHTML property of nodeHolder to this string after the whole loop is completed.
(... and let the DOM renderer figure out how to best optimize the action)
In summary:
Cache your DOM selector.
Ditch the for loop, and go for a reverse while loop.
Only append your element to the DOM once. The DOM is almost always the bottleneck.
In this pattern, you can take advantage of a reverse loop:
//Cache the DOM element
var node = document.getElementById('nodeHolder'),
markup = [];
//Run the loop backwards for additional speed
var i = 10000;
while(i--){
markup.push('<div>Node ' + (i + 1) + '</div>');
}
//Reverse the array, join it, then set the innerHTML
node.innerHTML = markup.reverse().join('');
Working example: http://jsfiddle.net/ZAkMZ/3/
Reverse while loop speed reference: https://blogs.oracle.com/greimer/entry/best_way_to_code_a
jQuery version:
//Cache the DOM element
var node = $('#nodeHolder'),
markup = [];
//Run the loop backwards for additional speed
var i = 10000;
while(i--){
markup.push('<div>Node ' + (i + 1) + '</div>');
}
//Reverse the array, join it, then set the innerHTML
node.append(markup.reverse().join(''));
You definitely need to use DocumentFragment as suggested by #David Thomas.
The most efficient version as I see is this... cloneNode is always better than createElement, switching between shallow or deep copy (still faster than createEl) as required.
Not that it gives a significant gain but you should shy away from assignments when you can. Store data in variables only when you need to. Of course when it comes to readability it's a different thing.
var fragment = document.createDocumentFragment();
var tplEl = document.createElement('div');
var contentTpl = document.createTextNode('Node ');
for (var i = 1; i <= 1000; i++) {
var curNode = contentTpl.cloneNode(false);
curNode.nodeValue = curNode.nodeValue + i;
tplEl.cloneNode(false).appendChild(curNode);
fragment.appendChild(tplEl);
}
document.getElementById('nodeHolder').appendChild(fragment);
Note that adding the fragment to the nodeHolder adds all childs of fragment as childs to nodeHolder triggering only 1 flow as opposed to 1000 flows in your earlier code.
Reference: Speeding up JavaScript
May be this way:
for (var i = 0; i < 100; i+=1) {
$("<div/>").appendTo("body").attr({"class":'txtHolder'});
$(".txtHolder").append(i+1);
}
I went throught http://www.youtube.com/watch?v=mHtdZgou0qU speed up your javascript.
So i did this personal speed test:
var count = 50000000;
var testDummy;
// test 1
testDummy = 0;
var test1Start = new Date().getTime();
var i;
for (i=0;i<count;i++) {
testDummy++;
}
var test1End = new Date().getTime();
var test1Total = (test1End-test1Start);
// test 2
testDummy = 0;
var test2Start = new Date().getTime();
var i
for (i=count; i--;) {
testDummy++;
}
var test2End = new Date().getTime();
var test2Total = (test2End-test2Start);
debug(
"test1\n" +
"total: " + test1Total + "\n" +
"test2\n" +
"total: " + test2Total
);
I get not significant results, like sometimes they are even and sometimes not.
My question is, if i use for loop like this: "for(i=count;i--;)" is it really faster ?
Am i doing something wrong in my tests.
Thanks for your help!
(I'd write this as a comment, but it'd be too long.)
First: Worrying about the efficiency of a for loop is almost always a waste of (your own) time. What's inside of the loop usually has much more impact on performance than the details of how the loop is specified.
Second: What browser(s) did you test with? Different browsers will show different performance profiles; even different versions of the same browser will differ.
Third: It's not out of the question that the JavaScript engine optimized your loops out of the picture. A JavaScript compiler could simply look at the loop and decide to set testDummy to 50000000 and be done with it.
Fourth: If you really want to split hairs on performance, I'd try for(i=count; --i != 0;) as well as for(i=count;i--;). The former may save a machine instruction or two, because executing the subtraction (in the predecrement step) may automatically set a hardware flag indicating that the result was 0. That flag is potentially wasted when you're using the postdecrement operator, because it wouldn't be examined until the start of the next iteration. (The chances that you'd be able to notice the difference are slim to none.)
Well...
for( i=0 ; i < len ; i++ )
is practically the same as
for( i = len ; i-- ; )
Lets describe it:
case 1:
let i be 0
boolean expression
let i be i + 1
case 2:
let i be len
let i be i - 1
cast i to boolean (type coersion) and interpret it.
The difference should be minute and depends entirely on how efficient type coersion is compared to a normal boolean expression.
Incidentally, test this:]
var i = count;
while( i-- ) {}
There's nothing wrong with your tests.
The blocks that you are testing are very-near identical, meaning the difference in execution speeds are going to be trivial. In both examples, a variable (i) is set to a fixed value and looped until it reaches a fixed value (count). The only thing that differs is i++ and i--, which in terms of speed, I think are practically the same.
The thing you have to be careful of (not do) is calculate the "loop until" value inside the loop definition.
I have made some tests too, here are the results.
In many articles, books authors propose that "optimized" loops are faster.
It seems that modern browsers have some optimizations for "normal" loops.
Firefox 13.0.1
Normal Loop: 0.887
Opt1: 1.025
Opt2: 1.098
Opt3: 1.399
Chrome 19.0.1
Normal Loop: 3.349
Opt1: 3.12
Opt2: 3.109
Opt3: 3.095
IE8
Over 12sec...
Repeatedly crashed during tests.
<script type="text/javascript">
function p(p) { console.log(p); }
// function p(p) { document.write(p); }
var testFn = function(num, niz, fn) {
var start = new Date().getTime();
fn(num, niz);
var result = (new Date().getTime() - start) / 1000;
return result;
}
function normalLoop(num, niz) {
for (var i = 0; i < niz.length; i++) {
niz[i] = 'a' + i;
}
}
function opt1(num, niz) {
var len = niz.length;
for (var i = 0; i < len; i++) {
niz[i] = 'a' + i;
}
}
function opt2(num, niz) {
for (var i = niz.length; i--;) {
niz[i] = 'a' + i;
}
}
function opt3(num, niz) {
while(i--) {
niz[i] = 'a' + i;
}
}
var niz = [];
var num = 10000000;
for (var i = 0; i < num; i++) { niz.push(i); };
p('Normal Loop: ' + testFn(num, niz, normalLoop));
p('Opt1: ' + testFn(num, niz, opt1));
p('Opt2: ' + testFn(num, niz, opt2));
p('Opt3: ' + testFn(num, niz, opt3));
</script>
I've read that if I have a for loop, I should not use string concation because it's slow. Such as:
for (i=0;i<10000000;i++) {
str += 'a';
}
And instead, I should use Array.join(), since it's much faster:
var tmp = [];
for (i=0;i<10000000;i++) {
tmp.push('a');
}
var str = tmp.join('');
However, I have also read that string concatention is ONLY a problem for Internet Explorer and that browsers such as Safari/Chrome, which use Webkit, actually perform FASTER is using string concatention than Array.join().
I've attempting to find a performance comparison between all major browser of string concatenation vs Array.join() and haven't been able to find one.
As such, what is faster and more efficient JavaScript code? Using string concatenation or Array.join()?
It appears Array.join() for the most part across browsers is faster.
Current gen Browsers
FF3 array.join is ~2x faster
Safari 3 array.join ~1.5x faster
Opera 9 += ~3x faster
ie6 array.join ~6x faster
ie7 array.join ~4x faster
Next gen browsers
FF3.1 += array.join equal in speed
Chrome += ~1.25x faster
IE8 array.join ~1.6x faster
Webkit array.join ~2x faster
Test results here: http://www.learningjquery.com/2009/03/43439-reasons-to-use-append-correctly
My feeling is that since there's no "nice" way to do a feature check to see whether string concatenation is slow, and because Array.join() is not terrible in non-IE browsers, using the array approach is good because your pages will behave pretty well everywhere and you won't have a mess to maintain (and you can avoid browser sniffing).
Now if you're implementing a framework or a library and not just a feature or two on a site, then more fine tuning might be called for.
Finally, I suggest going back to google and reading some of the results you get from a search for "javascript string concatenation performance." I found several good ones and I only got through half the first SRP. Read the comments on the blog posts too because often you pick up interesting links there.
I was curious not only about string concatenation, but about parsing as well. Here are some results regarding array.join, string+=, charAt() and array[].
Short summary
joining array is roughly 2.5-6 times quicker than string+=
array[] is 1.5-3 times quicker than charAt() (and splitting string to array is not an expansive operation)
Raw results
Chrome v23 FF 17 IE8
fill array(100k) 43 15 270 ms
join array(100k) 33 5 43 ms
string+=(100k) 223 12 118 ms
charAt(488.89k * 100) 517 462 75467 ms
split(488.89k) 12 34 279 ms
array[](488.89k * 100) 267 323 27343 ms
HTML code to test
<table>
<col/>
<col align="right"/>
<col/>
<script type="text/javascript" >
function test(what, action) {
var start = new Date(), end
document.write("<tr><td>" + what + "</td>")
action()
end = new Date()
document.write("<td>" + (end.getTime() - start.getTime()) + "</td> <td>ms</td></tr>\n")
}
var alen = 100000, efAlen, nIter = 100
var s = "" // test string for charAt, s[pos]
var ss = "" // subject ss += "??"
var as = [] // source for s = as.join("")
var slen // slen = s.length
var a // a = s.split("")
var maxSlen = 1000*1000
test("fill array(" + (alen / 1000) + "k)", function() {
slen = 0
for( var i = 0; i < alen; i++ ) {
var v = "" + i
slen += v.length
if( slen < maxSlen )
efAlen = (i + 1)
as[i] = v
}
})
test("join array(" + (efAlen / 1000) + "k)", function() {
s = as.slice(0, efAlen).join("")
slen = Math.min(s.length, maxSlen)
})
test("string+=(" + (efAlen / 1000) + "k)", function() {
for( var i = 0; i < efAlen; i++ ) {
ss += as[i]
}
if( ss != s )
document.write("ss.length:" + ss.length + ", s.length:" + s.length)
})
test("charAt(" + (slen / 1000) + "k * " + nIter + ")", function() {
for( var i = 0; i < nIter; i++ ) {
for( var p = 0; p < slen; p++ ) {
var c = s.charAt(p)
}
}
})
test("split(" + (slen / 1000) + "k)", function() {
a = s.split("")
})
test("array[](" + (slen / 1000) + "k * " + nIter + ")", function() {
for( var i = 0; i < nIter; i++ ) {
for( var p = 0; p < slen; p++ ) {
var c = a[p]
}
}
})
</script>
</table>