57

For the purpose of this question lets say we need to append() 1000 objects to the body element.

You could go about it like this:

for(x = 0; x < 1000; x++) {
    var element = $('<div>'+x+'</div>');
    $('body').append(element);
}

This works, however it seems inefficient to me as AFAIK this will cause 1000 document reflows. A better solution would be:

var elements = [];
for(x = 0; x < 1000; x++) {
    var element = $('<div>'+x+'</div>');
    elements.push(element);
}
$('body').append(elements);

However this is not an ideal world and this throws an error Could not convert JavaScript argument arg 0 [nsIDOMDocumentFragment.appendChild]. I understand that append() can't handle arrays.

How would I using jQuery (I know about the DocumentFragment node, but assume I need to use other jQuery functions on the element such as .css()) add a bunch of objects to the DOM at once to improve performance?

George Reith
  • 13,132
  • 18
  • 79
  • 148
  • [`append()`](http://api.jquery.com/append/) does not accept an array of strings. – epascarello Aug 24 '12 at 14:55
  • @GeorgeReith the answer you approved is even slower than your first approach. Take a look at the console logs in this jsfiddle http://jsfiddle.net/du2TN/2/ – davids Aug 24 '12 at 15:23
  • @davids interesting, although it seems my original code works in jQuery 1.8 and is the fastest of the jQuery methods. (updated your JSfiddle) – George Reith Aug 24 '12 at 15:29
  • Nice to know :) Anyway, @jAndi and jackwander's solution is much faster. But if you prefer to use jQuery, whatever fits you :) – davids Aug 24 '12 at 15:35
  • @davids I need to use jQuery as they must be jQuery objects so that I can use other jQuery functions in them. – George Reith Aug 24 '12 at 16:57

9 Answers9

70

You could use an empty jQuery object instead of an array:

var elements = $();
for(x = 0; x < 1000; x++) {
    elements = elements.add('<div>'+x+'</div>');
    // or 
    // var element = $('<div>'+x+'</div>');
    // elements = elements.add(element);
}
$('body').append(elements);

This might be useful if you want to do stuff with newly generated element inside the loop. But note that this will create a huge internal stack of elements (inside the jQuery object).


It seems though that your code works perfectly fine with jQuery 1.8.

Felix Kling
  • 795,719
  • 175
  • 1,089
  • 1,143
  • My actual testing solution is a little more complicated, each element should be a jquery object in its own right so that you can perform other jQuery functions on it. My bad for simplifying the test case too much. Updated question to reflect this. – George Reith Aug 24 '12 at 14:58
  • But then using `.add` and inserting the final jQuery object should work nevertheless. – Felix Kling Aug 24 '12 at 14:59
  • Ooops you are right, I forgot that `add` doesn't update the original copy. – George Reith Aug 24 '12 at 15:02
  • 3
    @George: Apparently the `.append` behaviour changed with jQuery 1.8. It works with arrays of jQuery objects as well now. This is your code: http://jsfiddle.net/7Q6wm/2/. I assume you used an older version. – Felix Kling Aug 24 '12 at 15:15
  • So i'm going to say this answer is actually invalid for 1.8. I spent some frustrating minutes figuring this out until i saw the mentioning of 1.8. May want to go further and mention this **does not** work in 1.8. – Derek Adair Nov 09 '14 at 19:00
  • I was referring to your answer, not the original attempt in the question - but i actually had a mistake. Its `elements = elemetns.add($div)` not `elements.add($div)` – Derek Adair Nov 10 '14 at 18:02
  • @Derek: Mmh, I must have been posting the wrong link. Yeah, it works fine: http://jsfiddle.net/eb69sdbz/. – Felix Kling Nov 10 '14 at 18:32
  • 1
    doesn't work in older versions of jQuery, use the document.createDocumentFragment method described by @jackwanders – Scott Jungwirth Oct 20 '15 at 20:55
  • @FelixKling your previous example could inspire naive JS developers to introduce XSS attacks if they’re wrapping HTML around user data, e.g., `elements = elements.add('
    '+usernames[i]+'
    ');`—as you reference 1.8 now supports the other way, people could create objects like the following `elements.push($('
    ', {text: usernames[i]}));` where `text` HTML-escapes the input for safe insertion.
    – MrColes Nov 11 '15 at 22:16
16

You could just call

$('body').append(elements.join(''));

Or you can just create a large string in the first place.

var elements = '';
for(x = 0; x < 1000; x++) {
    elements = elements + '<div>'+x+'</div>';
}
$(document.body).append(elements);

Like you mentioned, probably the most "correct" way is the usage of a DocFrag. This could look like

var elements = document.createDocumentFragment(),
    newDiv;
for(x = 0; x < 1000; x++) {
    newDiv = document.createElement('div');
    newDiv.textContent = x;
    elements.append( newDiv );
}
$(document.body).append(elements);

.textContent is not supported by IE<9 and would need an conditional check to use .innerText or .text instead.

Joseph Silber
  • 214,931
  • 59
  • 362
  • 292
jAndy
  • 231,737
  • 57
  • 305
  • 359
  • @JosephSilber: nothing wrong with using `+=` of course. I just wanted to be more explicit and expresive here. – jAndy Aug 24 '12 at 14:58
  • Thanks Andy but I fear I oversimplified my test case (my bad), I should of made each element its own object so that you can perform jQuery functions like `.css()` on them. I updated the test case in the question. – George Reith Aug 24 '12 at 14:59
  • Shouldn't that be `newDiv.textContent = x;`? – Joseph Silber Aug 24 '12 at 15:09
9

Upgrade to jQuery 1.8, this works as intended:

​$('body')​.append([
    '<b>1</b>',
    '<i>2</i>'   
])​;​
Alexey Lebedev
  • 11,988
  • 4
  • 39
  • 47
9

Since $.fn.append takes a variable number of elements we can use apply to pass the array as arguments to it:

el.append.apply(el, myArray);

This works if you have an array of jQuery objects. According to the spec though you can append an array of elements if you have the DOM elements. If you have an array of html strings you can just .join('') them and append them all at once.

Jethro Larson
  • 2,337
  • 1
  • 24
  • 24
3

A slight change to your second approach:

var elements = [],
newDiv;
for (x = 0; x < 1000; x++) {
    newDiv = $('<div/>').text(x);
    elements.push(newDiv);
}
$('body').append(elements);

$.append() certainly can append an array: http://api.jquery.com/append/

.append(content) | content: One or more additional DOM elements, arrays of elements, HTML strings, or jQuery objects to insert at the end of each element in the set of matched elements.

Draculater
  • 2,280
  • 1
  • 24
  • 29
  • 3
    jQuery was very strict in this regard. When it says "array of elements", it really means DOM elements, not jQuery objects. And indeed, using 1.7.2, this code does not work, but it works in 1.8. – Felix Kling Aug 24 '12 at 15:16
3

Sometimes, jQuery isn't the best solution. If you have a lot of elements to append to the DOM, documentFragment is a viable solution:

var fragment = document.createDocumentFragment();
for(var i = 0; i < 1000; i++) {
    fragment.appendChild(document.createElement('div'));
}
document.getElementsByTagName('body')[0].appendChild(fragment);
jackwanders
  • 15,612
  • 3
  • 40
  • 40
  • Doesn't jQuery use document fragment internally in the case you pass in an array? From the error above it definitely seems so. – Mitar Nov 26 '13 at 20:26
  • Thanks! This worked for me; I'm working on a project that is using an older version of jQuery that didn't handle arrays but works fine with createDocumentFragment. – Scott Jungwirth Oct 20 '15 at 20:54
0

If you're going for raw performance then I would suggest pure JS, though some would argue that your development performance is more important than your site's/program performance. Check this link for benchmarks and a showcase of different DOM insertion techniques.

edit:

As a curiosity, documentFragment proves to be one of the slowest methods.

MalSu
  • 541
  • 3
  • 11
  • that jsPerf is wrapping `documentFragment` in a `$()` call, which is probably what's slowing it down. `documentFragment` is still useful if you go for a pure JS solution. – jackwanders Aug 24 '12 at 15:01
  • This is test is a weird mix of DOM and jQuery, I don't thing it is very conclusive. – Felix Kling Aug 24 '12 at 15:01
  • It uses jQuery's each, but the actual appending is done using different methods. – MalSu Aug 24 '12 at 15:03
0

I would use native Javascript, normally much faster:

var el = document.getElementById('the_container_id');
var aux;
for(x = 0; x < 1000; x++) {
    aux = document.createElement('div');
    aux.innerHTML = x;
    el.appendChild(aux);
}

EDIT:

There you go a jsfiddle with different options implemented. The @jackwander's solution is, clearly, the most effective one.

davids
  • 6,259
  • 3
  • 29
  • 50
  • you're still doing 1000 separate `appendChild` operations, which will be slow. – jackwanders Aug 24 '12 at 15:04
  • @jackwanders you are completely right, I've implemented your solution mine one and is clearly faster, it can be seen in the jsfiddle I've attached – davids Aug 24 '12 at 15:15
0

I know, the question is old, but maybe it helps others.

Or simple use ECMA6 spread operator:

$('body').append(...elements);
shaedrich
  • 5,457
  • 3
  • 26
  • 42