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

JavaScript Tagged in JavaScript Dec 9, 2013 7 mins read by Todd Motto

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:

var 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:

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

or…

var 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?

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.

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:

var divs = document.querySelectorAll('div');
for (var i = 0; i < divs.length; i++) {
  // access to individual element:
  var 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):

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

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

// NodeList collection
var 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:

var divs = document.querySelectorAll('div');
for (var i = 0; i < divs.length; i++) {
    var self = divs[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:

var 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:

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

// outputs 3
console.log(nodes);

// create a new element
var 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:

var myNodeList = document.querySelectorAll('div');
var myArray = []; // empty Array
for (var i = 0; i < myNodeList.length; i++) {
    var 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:

var arr = [];
var divs = document.querySelectorAll('div');
for(var 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
var 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:

var 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:

var 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.

var divs = document.querySelectorAll('div');
var 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:

var divs = document.querySelectorAll('div');
Array.prototype.forEach.call(divs, function (item) {
  // Individual access to element:
  var 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:

var divs = document.querySelectorAll('div');
[].forEach.call(divs, function (item) {
  // Individual access to element:
  var 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:

var 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 :)

Angular Online Courses Angular shields
Ultimate Angular

Become an Angular expert

Online courses that give you the knowledge to master Angular and build real world applications.

Explore Courses Navigation arrow