Skip to main content

JavaScript DOM

JavaScript DOM Collections

Written by Published

querySelectorAll returns a NodeList, which looks like an array but is not one.

A NodeList has a length and can be indexed, so it feels like an array.

It does not have map or filter, which is where confusion starts.

Example

Example

javascript

document.body.innerHTML = "<p>A</p><p>B</p>";

const items = document.querySelectorAll("p");

console.log(items.length);
console.log(Array.isArray(items));

The output is 2 then false.

NodeList and HTMLCollection

querySelectorAll returns a static NodeList, which has forEach.

children and getElementsByTagName return a live HTMLCollection, which does not.

Static means it does not update when the page changes; live means it does.

Syntax

Syntax

javascript

const list = document.querySelectorAll("p");
const array = Array.from(list);

Array.from turns either kind into a real array.

Static Versus Live

This difference causes real bugs, so it is worth seeing directly.

Example

Example

javascript

document.body.innerHTML = '<div id="box"><p>A</p></div>';

const box = document.getElementById("box");
const staticList = document.querySelectorAll("#box p");
const liveList = box.getElementsByTagName("p");

box.append(document.createElement("p"));

console.log(staticList.length);
console.log(liveList.length);

The static list still says 1; the live one says 2.

forEach Works on a NodeList

This is the one array method a NodeList does have.

Example

Example

javascript

document.body.innerHTML = "<p>A</p><p>B</p>";

let text = "";
document.querySelectorAll("p").forEach(function (p) {
  text += p.textContent;
});

console.log(text);

The output is AB.

Getting a Real Array

Array.from or spread unlocks every array method.

Example

Example

javascript

document.body.innerHTML = "<p>A</p><p>B</p><p>C</p>";

const texts = Array.from(document.querySelectorAll("p")).map(function (p) {
  return p.textContent;
});

console.log(texts.join("-"));

The output is A-B-C.

Filtering Elements

Once it is an array, filter works as normal.

Example

Example

javascript

document.body.innerHTML =
  '<p class="on">A</p><p>B</p><p class="on">C</p>';

const on = [...document.querySelectorAll("p")].filter(function (p) {
  return p.classList.contains("on");
});

console.log(on.length);

A CSS selector could have done this too, but the pattern is general.

A Live Collection in a Loop

Removing elements while looping over a live collection skips items.

The list shrinks underneath the loop as it runs.

Example

Example

javascript

document.body.innerHTML = '<div id="box"><p>A</p><p>B</p></div>';

const box = document.getElementById("box");

Array.from(box.getElementsByTagName("p")).forEach(function (p) {
  p.remove();
});

console.log(box.children.length);

Copying to an array first makes the loop safe.

Complete Example

Complete Example

html

<!DOCTYPE html>
<html>
<head>
  <title>JavaScript DOM Collections</title>
</head>
<body>

  <h1>DOM Collections</h1>

  <p>One</p>
  <p>Two</p>
  <p>Three</p>

  <p id="out"></p>

  <script>
    const items = document.querySelectorAll("p");
    const texts = Array.from(items).map(function (p) {
      return p.textContent;
    });

    document.getElementById("out").textContent = texts.length + " paragraphs";
  </script>

</body>
</html>

Try It Yourself

Run the above example in the Try It Editor.

Try filtering:

Keep only the texts longer than three characters before counting.

Important Points

  • querySelectorAll returns a static NodeList.
  • children returns a live HTMLCollection.
  • A NodeList has forEach but not map or filter.
  • Array.from or spread gives a real array.
  • Copy a live collection before removing elements from it.

Conclusion

DOM collections are array-like but not arrays.

Converting to an array once removes the whole class of problem.

The live versus static difference is a favourite interview question.