querySelector and querySelectorAll vs getElementsByClassName and getElementById in JavaScript

Solution 1:

For this answer, I refer to querySelector and querySelectorAll as querySelector* and to getElementById, getElementsByClassName, getElementsByTagName, and getElementsByName as getElement*.

A lot of this information can be verified in the specification, a lot of it is from various benchmarks I ran when I wrote it. The spec: https://dom.spec.whatwg.org/

Main Differences

  1. querySelector* is more flexible, as you can pass it any CSS3 selector, not just simple ones for id, tag, or class.
  2. The performance of querySelector* changes with the size of the DOM that it is invoked on. To be precise, querySelector* calls run in O(n) time and getElement* calls run in O(1) time, where n is the total number of all children of the element or document it is invoked on. This fact seems to be the least well-known, so I am bolding it.
  3. The return types of these calls vary. querySelector and getElementById both return a single element. querySelectorAll and getElementsByName both return NodeLists. The older getElementsByClassName and getElementsByTagName both return HTMLCollections. NodeLists and HTMLCollections are both referred to as collections of elements.
  4. Collections can contain references to elements in the DOM, or copies of elements. getElement* calls return collections of references, whereas querySelectorAll results contain copies of the elements. These are referred to as "live" and "static" collections respectively. This is NOT related to the types that they return.

These concepts are summarized in the following table.

Function               | Live? | Type           | Time Complexity
querySelector          |       | Element        |  O(n)
querySelectorAll       |   N   | NodeList       |  O(n)
getElementById         |       | Element        |  O(1)
getElementsByClassName |   Y   | HTMLCollection |  O(1)
getElementsByTagName   |   Y   | HTMLCollection |  O(1)
getElementsByName      |   Y   | NodeList       |  O(1)

Details, Tips, and Examples

  • HTMLCollections are not as array-like as NodeLists and do not support .forEach(). I find the spread operator useful to work around this:

    [...document.getElementsByClassName("someClass")].forEach()

  • Every element, and the global document, have access to all of these functions except for getElementById and getElementsByName, which are only implemented on document.

  • Chaining getElement* calls instead of using querySelector* will improve performance, especially on very large DOMs. Even on small DOMs and/or with very long chains, it is generally faster. However, unless you know you need the performance, the readability of querySelector* should be preferred. querySelectorAll is often harder to rewrite, because you must select elements from the NodeList or HTMLCollection at every step. For example, the following code does not work:

    document.getElementsByClassName("someClass").getElementsByTagName("div")

    because you can only use getElements* on single elements, not collections. For example:

    document.querySelector("#someId .someClass div")

    could be written as:

    document.getElementById("someId").getElementsByClassName("someClass")[0].getElementsByTagName("div")[0]

    Note the use of [0] to get just the first element of the collection at each step that returns a collection, resulting in one element at the end just like with querySelector.

  • Since all elements have access to both querySelector* and getElement* calls, you can make chains using both calls, which can be useful if you want some performance gain, but cannot avoid a querySelector that can not be written in terms of the getElement* calls.

  • Though it is generally easy to tell if a selector can be written using only getElement* calls, there is one case that may not be obvious:

    document.querySelectorAll(".class1.class2")

    can be rewritten as

    document.getElementsByClassName("class1 class2")

  • Using getElement* on a static element fetched with querySelector* will result in an element that is live with respect to the static subset of the DOM copied by querySelector, but not live with respect to the full document DOM... this is where the simple live/static interpretation of elements begins to fall apart. You should probably avoid situations where you have to worry about this, but if you do, remember that querySelector* calls copy elements they find before returning references to them, but getElement* calls fetch direct references without copying.

  • querySelector* and getElementById traverse elements in preorder, depth-first, called "tree order" in the specification. With other getElement* calls it is not clear to me from the specification - they may be the same as tree order, but getElementsByClassName(".someClass")[0] may not reliably give the same result in every browser. getElementById("#someId") should though, even if you have multiple copies of the same id on your page.

Solution 2:

I would like to know what exactly is the difference between querySelector and querySelectorAll against getElementsByClassName and getElementById?

The syntax and the browser support.

querySelector is more useful when you want to use more complex selectors.

e.g. All list items descended from an element that is a member of the foo class: .foo li

document.querySelector("#view:_id1:inputText1") it doesn't work. But writing document.getElementById("view:_id1:inputText1") works. Any ideas why?

The : character has special meaning inside a selector. You have to escape it. (The selector escape character has special meaning in a JS string too, so you have to escape that too).

document.querySelector("#view\\:_id1\\:inputText1")

Solution 3:

collecting from Mozilla Documentation:

The NodeSelector interface This specification adds two new methods to any objects implementing the Document, DocumentFragment, or Element interfaces:

querySelector

Returns the first matching Element node within the node's subtree. If no matching node is found, null is returned.

querySelectorAll

Returns a NodeList containing all matching Element nodes within the node's subtree, or an empty NodeList if no matches are found.

and

Note: The NodeList returned by querySelectorAll() is not live, which means that changes in the DOM are not reflected in the collection. This is different from other DOM querying methods that return live node lists.

Solution 4:

About the differences, there is an important one in the results between querySelectorAll and getElementsByClassName: the return value is different. querySelectorAll will return a static collection, while getElementsByClassName returns a live collection. This could lead to confusion if you store the results in a variable for later use:

  • A variable generated with querySelectorAll will contain the elements that fulfilled the selector at the moment the method was called.
  • A variable generated with getElementsByClassName will contain the elements that fulfilled the selector when it is used (that may be different from the moment the method was called).

For example, notice how even if you haven't reassigned the variables aux1 and aux2, they contain different values after updating the classes:

// storing all the elements with class "blue" using the two methods
var aux1 = document.querySelectorAll(".blue");
var aux2 = document.getElementsByClassName("blue");

// write the number of elements in each array (values match)
console.log("Number of elements with querySelectorAll = " + aux1.length);
console.log("Number of elements with getElementsByClassName = " + aux2.length);

// change one element's class to "blue"
document.getElementById("div1").className = "blue";

// write the number of elements in each array (values differ)
console.log("Number of elements with querySelectorAll = " + aux1.length);
console.log("Number of elements with getElementsByClassName = " + aux2.length);
.red { color:red; }
.green { color:green; }
.blue { color:blue; }
<div id="div0" class="blue">Blue</div>
<div id="div1" class="red">Red</div>
<div id="div2" class="green">Green</div>