Javascript Icon Get 62% off the JavaScript Master bundle

See the bundle then add to cart and your discount is applied.

0 days
00 hours
00 mins
00 secs

Write JavaScript like a pro. Javascript Icon

Follow the ultimate JavaScript roadmap.

A comprehensive dive into NodeLists, Arrays, converting NodeLists and understanding the DOM

Manipulating the DOM is JavaScript’s role when developing websites and applications, and we do this by grabbing collections of elements called NodeLists. NodeLists are captured using a selector of some kind (jQuery or native JS), but do you really understand NodeLists and their differences from an Array collection of DOM nodes? This post is here to clear a few things up and hopefully answer some questions.

If you’re a jQuery user, you’re probably used to doing this:

const divs = $('div');

This then introduces the black box scenario for many new JavaScript developers, jQuery “just works”. If you’re one of those people, you’re probably going to tread on a nail one day and realise you’d wished you learned how the DOM really works with JavaScript, so here’s a quick lesson for you if you’re in that boat.

For JavaScript developers (yay), there are a few ways to do the above as we dig a little deeper into the DOM’s core:

const divs = document.getElementsByTagName('div');

or…

const divs = document.querySelectorAll('div');

All of these (apart from jQuery) return a NodeList. Any JavaScript/jQuery developer will have played around with the old document.getElementsByTagName() method, but do they know it returns a NodeList rather than an Array? And what difference/importance does this really play?

Angular Directives In-Depth eBook Cover

Free eBook

Directives, simple right? Wrong! On the outside they look simple, but even skilled Angular devs haven’t grasped every concept in this eBook.

  • Green Tick Icon Observables and Async Pipe
  • Green Tick Icon Identity Checking and Performance
  • Green Tick Icon Web Components <ng-template> syntax
  • Green Tick Icon <ng-container> and Observable Composition
  • Green Tick Icon Advanced Rendering Patterns
  • Green Tick Icon Setters and Getters for Styles and Class Bindings

A lot. If you’ve never heard of NodeLists, or haven’t learned about them but are using jQuery on a daily basis, then you need to learn what you’re really dealing with underneath for many reasons.

Understanding the DOM and JavaScript will help you write much better JavaScript.

What is a NodeList?

NodeLists are very similar to Array collections of elements, often referred to as “array-like”, but with a subtle difference - you’re missing out on a lot of JavaScript functionality by keeping your collection as a NodeList, such as true Array iteration and Prototypal methods.

Angular Directives In-Depth eBook Cover

Free eBook

Directives, simple right? Wrong! On the outside they look simple, but even skilled Angular devs haven’t grasped every concept in this eBook.

  • Green Tick Icon Observables and Async Pipe
  • Green Tick Icon Identity Checking and Performance
  • Green Tick Icon Web Components <ng-template> syntax
  • Green Tick Icon <ng-container> and Observable Composition
  • Green Tick Icon Advanced Rendering Patterns
  • Green Tick Icon Setters and Getters for Styles and Class Bindings

Array iteration

What’s iteration? This means looping over your collection of elements, which you can then do something with each individual element’s value or index. Looping over a NodeList is the exact same as an Array when using a regular for loop:

const divs = document.querySelectorAll('div');
for (let i = 0; i < divs.length; i++) {
  // access to individual element:
  const elem = divs[i];
}

But when we introduce the modern JavaScript forEach() method, problems arise with the native API itself, the forEach() method is to be used when iterating over Arrays (BTW, you can use forEach() for Arrays in older browsers with a Polyfill, see end of article):

const myArray = [1,2,3,4,5];
myArray.forEach(function (item) {
  // access to individual element
  const elem = item;
});

So that should work great when it comes to a NodeList, they’re pretty similar. Take the following example:

// NodeList collection
const divs = document.querySelectorAll('div');

// let's casually loop over the NodeList
divs.forEach(function () {
  
});

BAM!

Uncaught TypeError: Object #<NodeList> has no method 'forEach'

What happened? Why is my code broken? Waaaahhh?” the recent jQuery convert says.

You can’t manipulate NodeLists the same way you can an Array.

Prototypal methods

Arrays come with a bunch of awesomely inherited prototypal methods, things like splice(), push(), join(), indexOf() and many more. When our collections are NodeLists, we miss out on all this goodness. Check out MDN for a comprehensive list of methods.

Which means we cannot remove an item from a NodeList like you’d simply expect:

const divs = document.querySelectorAll('div');
for (let i = 0; i < divs.length; i++) {
    divs.splice(i, 1); // Remove this element from the NodeList
}

Uh oh…

Uncaught TypeError: Object #<NodeList> has no method 'splice'

What isn’t a NodeList?

A NodeList isn’t an Array (applause).

NodeLists are actually really interesting collections of Nodes, and are separate to their close cousin Arrays for a few good reasons, they can contain what we call live Nodes.

If I had the following HTML (3 divs):

<div></div>
<div></div>
<div></div>

And ran a document.getElementsByTagName() method, this will return a live collection:

const nodes = document.getElementsByTagName('div');

// outputs 3
console.log(nodes);

If I were to do the following and insert a new div element into the page:

const nodes = document.getElementsByTagName('div');

// outputs 3
console.log(nodes);

// create a new element
const newDiv = document.createElement('div');
document.body.appendChild(newDiv);

// outputs 4
console.log(nodes);

As if by magic, our nodes collection has automatically updated. I’m sure you can see the use of that, so you might not always want to convert a NodeList to Array.

Converting NodeLists to Arrays

The plan of attack here really does vary depending entirely on your browser support and use case for that particular NodeList/Array.

Browser Support

If you need IE8 and below support, the easiest way of converting a NodeList to an Array is pushing each element from a NodeList into a new Array:

const myNodeList = document.querySelectorAll('div');
const myArray = []; // empty Array
for (let i = 0; i < myNodeList.length; i++) {
    const self = myNodeList[i];
    myArray.push(self);
}

And you’re all done. It’s a nice and simple process. I absolutely love this method, as it still keeps your original NodeList reference if you need it, for instance keeping a tab on your live NodeList collection. Please note though, using document.querySelectorAll() returns a static NodeList, not live, therefore it won’t automatically update. However, document.getElementsByTagName() will keep a live record, but getting elements by their tag name is slowly dying. I personally would’ve liked to have seen live Nodes in querySelectorAll.

Moving swiftly forward, you’d be interested (maybe) to know that some performance/speed tests were done and the quickest method (apparently) of converting a NodeList to Array is:

const arr = [];
const divs = document.querySelectorAll('div');
for(let i = divs.length; i--; arr.unshift(divs[i]));

Check out some of the other NodeList to Array perf tests.

If you’re fortunate enough to not care about IE8 and below, then you can use a neat trick to convert your NodeList instantly using Array.prototype.slice.call():

// 'divs' is now an Array
const divs = Array.prototype.slice.call(document.querySelectorAll('div'));

Accessing the Prototype Object here, we grab the slice() method, and pass our NodeList into it. This API then internally converts it to an Array using the slice() method (which returns a new Array). It cleverly pushes each Node into a new Array, yay!

Now we can access all the Array methods and use the forEach() method as intended:

const divs = Array.prototype.slice.call(document.querySelectorAll('div'));
divs.forEach(function () {
  //...
});

And no more TypeErrors, everything is good.

We can shorten this entire declaration however using an empty Array, which has access to the Prototype methods:

const divs = [].slice.call(document.querySelectorAll('div'));

… But I wouldn’t advise it, this can cause issues with other libraries, even though it’s sexier and shorter, use the long version and you’ll be writing more bulletproof code.

ECMAScript 6 Array.from()

The new ECMAScript 6 Harmony standard introduces the Array.from method which makes Array-like Objects (such as the NodeList) and other iterable Objects (such as an Object or String) to Array conversion a breeze.

const divs = document.querySelectorAll('div');
const arr = Array.from(divs); // Array of <div>s

More on the Array.from method.

Looping through NodeLists on-the-fly

For some time I thought it was pretty cool to do this, which takes the Prototypal methods one step further:

const divs = document.querySelectorAll('div');
Array.prototype.forEach.call(divs, function (item) {
  // Individual access to element:
  const elem = item;
});

Using the forEach() method and using call, again, this iterates over the NodeList is an Array fashion, almost converting it on the fly but never changing the original reference.

As above, we can use the shorthand empty array reference like so, but we’ve established that’s not a good idea:

const divs = document.querySelectorAll('div');
[].forEach.call(divs, function (item) {
  // Individual access to element:
  const elem = item;
});

Polyfill(s)

As promised, polyfill(s) for you to drop in:

array.forEach(), reference

if (!Array.prototype.forEach) {
  Array.prototype.forEach = function (fn, scope) {
    var i, len;
    for (i = 0, len = this.length; i < len; ++i) {
      if (i in this) {
        fn.call(scope, this[i], i, this);
      }
    }
  };
}

Dropping in the above will run a quick feature detect on the forEach method and patch the browser functionality for you, which means you can do this and it’ll work in every browser:

const myArray = [1,2,3,4,5];
myArray.forEach(function () {
  //...
});

Hooray for ECMAScript 5!

Summing up

I particularly don’t like iterating over the NodeList on the fly, my advice would be to always convert your NodeLists and then you’ll never have any issues at a later date or with other parts of your scripts. Again, the method that you choose to manipulate over your NodeLists is project and script dependent, so learn what each method does and make your decision wisely :)

Learn JavaScript the right way.

The most complete guide to learning JavaScript ever built.
Trusted by 82,951 students.

Todd Motto

with Todd Motto

Google Developer Expert icon Google Developer Expert

Related blogs 🚀

Free eBooks:

Angular Directives In-Depth eBook Cover

JavaScript Array Methods eBook Cover

NestJS Build a RESTful CRUD API eBook Cover