Interested in working with us? We are hiring!

See open positions

Unexpected behavior with jQuery

Timothée Boucher Written by Timothée Boucher, July 22, 2013

As perfect as jQuery may seem, sometimes its default behaviors end up being very surprising to the uninitiated.

During a routine code review, these few lines were pointed out as being weird because, even though there are 2 append() calls, nothing was evidently wrong from the UI.

if (this.summaryBar) {
    this.$('.slot-one').append(this.summaryBar.el, this.campaignGraph.el);
}

this.$('.slot-one').append(this.campaignGraph.el);

(NB: we’re using Backbone.js and these are Backbone views, so .el represents the DOM element for these views)

Dan wrote:

will this append the campaignGraph twice in the case that there is a summary bar?

Reading the code, it seemed to be that way but running the code, campaignGraph was added only once.

This called for some digging.


The reason is actually documented:

You can also select an element on the page and insert it into another:

$('.container').append($('h2'));

If an element selected this way is inserted into a single location elsewhere in the DOM, it will be moved into the target (not cloned).

If there is more than one target element, however, cloned copies of the inserted element will be created for each target after the first.

This actually makes some sense: an element is passed, so why not use it. But when called on multiple elements, we need to clone the argument to append it to multiple DOM locations.

However, because of that, append() doesn’t behave like you would expect when called multiple times.

Take this HTML:

<ul></ul>

What do you expect this JavaScript to do:

var li = $('<li>Hello</li>');
$('ul').append(li);
$('ul').append(li);
$('ul').append(li);
$('ul').append(li);

Until this morning, I would have expected to get four <li> elements inside the <ul> but it will have only the one I created since it’s never cloned:

<ul>
    <li>Hello</li>
</ul>

Even though it follows the documentation (mostly), the behavior is more unexpected when your selector matches multiple elements.

Let’s use the following HTML instead:

<ul></ul>
<ul></ul>
<ul></ul>

With the same JavaScript, the result is:

<ul>
    <li>Hello</li>
    <li>Hello</li>
    <li>Hello</li>
    <li>Hello</li>
</ul>
<ul>
    <li>Hello</li>
    <li>Hello</li>
    <li>Hello</li>
    <li>Hello</li>
</ul>
<ul>
    <li>Hello</li>
</ul>

I followed the tracks of this behavior in jQuery’s bug tracker and commit log and it dates from about two years ago. The reason is to avoid memory leaks. Sadly, the current side-effect is unintuitive.

Here is a pen you can play with:

See the Pen jQuery.append() unexpected behavior by AdRoll Dev (@adrolldev) on CodePen

1. The documentation is incorrect since it's actually the last element, not the first that ends up with the original element. But the main idea remains. (as a good open-source citizen, I submitted an edit to the documentation)

Photo credit: Ed Schipul