JavaScript vs jQuery HTML Collections

HTMLCollection objects are returned from document.getElementsByTagName, document.getElementsByName and document.getElementsByClassName methods (not supported in all browsers). Superficially, they’re similar to arrays since they have length property and elements can be accessed by [index]. However, they’re not arrays; methods such as push(), slice() and sort() are not supported.

Consider the following HTML document:


<body>
	<p>Paragraph 1</p>
	<p>Paragraph 2</p>
	<p>Paragraph 3</p>
</body>

Let’s grab every paragraph node using getElementsByTagName and a jQuery selector:


var pCollection = document.getElementsByTagName("p");
var pQuery = $("p");

console.log("pCollection.length: ", pCollection.length);
console.log("pQuery.length: ", pQuery.length);

Both return the same nodes so the collection length is 3:


pCollection.length: 3
pQuery.length: 3

We’ll now add another paragraph element to the document and look at the collections again:


// add new paragraph
var newp = document.createElement("p");
newp.appendChild(document.createTextNode("Paragraph 4"));
document.body.appendChild(newp);
// 
// display length
console.log("pCollection.length: ", pCollection.length);
console.log("pQuery.length: ", pQuery.length);

The result:


pCollection.length: 4
pQuery.length: 3

HTMLCollection objects are live. They are automatically updated whenever the underlying document changes. jQuery and most other JavaScript libraries use methods such as document.getElementsByTagName() but copy the resulting nodes to a real array. Therefore, it’s a query on the state of the document at that time: it is never updated.

There are advantages and disadvantages with both methods. For example, the following code causes an infinite loop since the HTMLCollection length increases as <p> elements are added:


var pCollection = document.getElementsByTagName("p");
for (var i = 0; i < pCollection.length; i++) {
	document.body.appendChild(pCollection[i].cloneNode(true));
}

That said, there may be situations when a faster, native live HTMLCollection is more useful than a static collection of jQuery nodes or repeatedly making the same selection. Fortunately, we can pass any collection to jQuery when we want to manipulate it, e.g.


var pCollection = document.getElementsByTagName("p");
// ... add nodes, do work, etc ...
$(pCollection).addClass("myclass");

jQuery and other libraries can cut development effort but always check whether it’s possible to write more efficient code in plain old JavaScript without incurring additional file requests and processing overheads.

Free book: Jump Start HTML5 Basics

Grab a free copy of one our latest ebooks! Packed with hints and tips on HTML5's most powerful new features.

  • Alejandro

    I don’t think that’s entirely accurate:
    – getElementsByTagName, getElementsByName and getElementsByClassName return a live NodeList.
    – Things like document.forms, document.links, etc. those do return an HTMLCollection.
    – There are also Static NodeLists.
    http://alebelcor.blogspot.com/2011/03/htmlcollections-nodelists.html

    • http://www.optimalworks.net/ Craig Buckler

      While the terminology is slightly different, NodeLists and HTMLCollections are indistinguishable when you’re programming in JavaScript.

      However, it’s interesting to note that .querySelectorAll() is static. It returns all nodes in the document at the time it’s run and won’t update.

      • Alexander Trefz

        The querySelectorAll thing is pretty logical -> John Resig was one of the guys that proposed querySelectorAll, and its whole use is to get static arrays of this selector very fast, in other words: its made to help JS-Libraries.

  • Phil

    A good point, I’ve seen a lot of this with code where, for instance, people create a jQuery object just to pull a value from a text field.

  • IT Mitică

    Even with
    var pQuery = $(“p”)[0];
    ?

  • IT Mitică

    Never mind the previous, I’ve misunderstood.

  • IT Mitică

    A good point.

    You’re saying jQuery passes the parameters by value, not by reference.

    But…

    While

    – the assignment operation for pCollection means this will became more of a handler, and that each iteration of pCollection means a complete reevaluation for the document.getElementsByTagName() function, which also means slower execution,

    – the assignment operation for pQuery means the code is actually executed on the spot, and each iteration pQuery means faster execution?

    • http://www.optimalworks.net/ Craig Buckler

      Not quite.

      document.getElementsByTagName(“p”) returns a live HTMLCollection. It’ll always hold every <p> node no matter how the document changes. The speed will remain fast because you’re not running JavaScript to handle it.

      This would be ideal if, say, you wanted to poll for changes every second. If you did that using jQuery, you’d need to repeat $(“p”). That would execute a new document.getElementsByTagName(“p”) and copy the nodes into an array. That could be noticeably slower on large documents.

      • IT Mitică

        Please correct me if I’m wrong.

        1. document.getElementsByTagName(“p”) returns a live NodeList, a type object, while HTMLCollection is an interface, which, to my understanding, it’s an object that offers methods and properties as a plus.

        2. It’s live == internal, hidden re-evaluation on every iteration, or is there another mechanism, underneath? It’s an internal mechanism that automatically rebuilds the NodeList, which means it’s hidden from me, but executed on every call (pCollection.length, for example), nontheless.

        3. To poll for changes more practical, you could use MutationEvent, but that leaves out IE8 and below.

  • molokoloco

    It’s a better practice to assign jQuery elements to variable like this
    $allParagraphs = $(‘p’);
    But if you manipulate hardly the DOM, you should not work with it… Yep.
    $(‘p’).length(); will always works…

    • http://www.optimalworks.net/ Craig Buckler

      I’m not sure I understand your comment?

      $allParagraphs = $(‘p’) would hold all <p> nodes at the moment it’s run. If you added or removed nodes from the document, $allParagraphs would never change. That might be useful or not depending on what you’re trying to achieve.

  • Ilya Vassilevsky

    OMG this is fantastic!

  • molokoloco

    Yes, $allParagraphs = $(‘p’) would hold all with distinct nodes as reference. So there only when creating/inserting/ordering main nodes elements that the $allParagraphs var is not updated.
    But if you change one node properties like $(‘p’).eq(2).text(‘New text’) you have no problem to fetch it with alert($allParagraphs[2].text()) …

    • http://www.optimalworks.net/ Craig Buckler

      The article illustrates the difference between live HTMLCollections and jQuery’s static array of nodes. If the document or selection doesn’t change, jQuery’s nodes will remain valid.

      However, if you removed a <p> node, $allParagraphs[2] may not exist. It’d would remain in jQuery’s array but most methods would ignore it.

  • Smola

    This was enlightening! Thanks Craig!

    As an aside, I’d love to see a series of posts that pit regular JS vs. jQuery for common tasks to see which is actually more efficient. I feel shamed that I’ve most likely been subtracting from run-time efficiency to decrease development time. =[

  • molokoloco

    Resolved, but only work for one tag at a time…
    var s = function(t) { return document.getElementsByTagName(t); };
    alert(s(‘p’).length);

    ;)

  • Han Wei

    Nice article!

    Minor nitpick:

    “…there may be situations when a faster, native live HTMLCollection is more useful than a static collection of jQuery nodes or repeatedly making the same selection.”

    This is misleadingly implies that the native HTMLCollection is faster than jQuery collections, which is never the case, because every access to an item in the HTMLCollection is as slow as running a new jQuery call, whereas accessing the essentially cached jQuery items is almost negligible time.

    While performance usually isn’t a concern at all in JavaScript, if it ever is a problem the culprit is almost *always* from touching the DOM, even something as simple as accessing an HTMLCollection. (For example, I recently encountered an issue where setting the value attribute of a textarea takes in excess of 15ms, which in a loop of 50-some times was causing the page to hang for almost a second, whereas the rest of the JavaScript being run combined took 1 or 2ms, max.)