How to convert a DOM node list to an array in Javascript?

I have a Javascript function that accepts a list of HTML nodes, but it expects a Javascript array (it runs some Array methods on that) and I want to feed it the output of Document.getElementsByTagName that returns a DOM node list.

Initially I thought of using something simple like:

Array.prototype.slice.call(list,0)

And that works fine in all browsers, except of course Internet Explorer which returns the error "JScript object expected", as apparently the DOM node list returned by Document.getElement* methods is not a JScript object enough to be the target of a function call.

Caveats: I don't mind writing Internet Explorer specific code, but I'm not allowed to use any Javascript libraries such as JQuery because I'm writing a widget to be embedded into 3rd party web site, and I cannot load external libraries that will create conflict for the clients.

My last ditch effort is to iterate over the DOM node list and create an array myself, but is there a nicer way to do that?

Answers:

Answer

NodeLists are host objects, using the Array.prototype.slice method on host objects is not guaranteed to work, the ECMAScript Specification states:

Whether the slice function can be applied successfully to a host object is implementation-dependent.

I would recommend you to make a simple function to iterate over the NodeList and add each existing element to an array:

function toArray(obj) {
  var array = [];
  // iterate backwards ensuring that length is an UInt32
  for (var i = obj.length >>> 0; i--;) { 
    array[i] = obj[i];
  }
  return array;
}

UPDATE:

As other answers suggest, you can now can use in modern environments the spread syntax or the Array.from method:

const array = [ ...nodeList ] // or Array.from(nodeList)

But thinking about it, I guess the most common use case to convert a NodeList to an Array is to iterate over it, and now the NodeList.prototype object has the forEach method natively, so if you are on a modern environment you can use it directly, or have a pollyfill.

Answer

In es6 you can just use as follows:

  • Spread operator

     var elements = [... nodelist]
    
  • Using Array.from

     var elements = Array.from(nodelist)
    

more reference at https://developer.mozilla.org/en-US/docs/Web/API/NodeList

Answer

Using spread (ES2015), it's as easy as: [...document.querySelectorAll('p')]

(optional: use Babel to transpile the above ES6 code to ES5 syntax)


Try it in your browser's console and see the magic:

for( links of [...document.links] )
  console.log(links);
Answer

Use this simple trick

<Your array> = [].map.call(<Your dom array>, function(el) {
    return el;
})
Answer

While it is not really a proper shim, since there is no spec requiring working with DOM elements, I've made one to allow you to use slice() in this manner: https://gist.github.com/brettz9/6093105

UPDATE: When I raised this with the editor of the DOM4 spec (asking whether they might add their own restrictions to host objects (so that the spec would require implementers to properly convert these objects when used with array methods) beyond the ECMAScript spec which had allowed for implementation-independence), he replied that "Host objects are more or less obsolete per ES6 / IDL." I see per http://www.w3.org/TR/WebIDL/#es-array that specs can use this IDL to define "platform array objects" but http://www.w3.org/TR/domcore/ doesn't seem to be using the new IDL for HTMLCollection (though it looks like it might be doing so for Element.attributes though it only explicitly states it is using WebIDL for DOMString and DOMTimeStamp). I do see [ArrayClass] (which inherits from Array.prototype) is used for NodeList (and NamedNodeMap is now deprecated in favor of the only item that would still be using it, Element.attributes). In any case, it looks like it is to become standard. The ES6 Array.from might also be more convenient for such conversions than having to specify Array.prototype.slice and more semantically clear than [].slice() (and the shorter form, Array.slice() (an "array generic"), has, as far as I know, not become standard behavior).

Answer

Today, in 2018, we could use the ECMAScript 2015 (6th Edition) or ES6, but not all browsers can understand it (for ex. IE does not understand all of it). If you want you could use ES6 as follows: var array = [... NodeList]; (as spread operator) or var array = Array.from(NodeList);.

In other case (if you can not use ES6) you can use the shortest way to convert a NodeList to an Array:

var array = [].slice.call(NodeList, 0);.

For example:

var nodeList = document.querySelectorAll('input');
//we use "{}.toString.call(Object).slice(8, -1)" to find the class name of object
console.log({}.toString.call(nodeList).slice(8, -1)); //NodeList

var array = [].slice.call(nodeList, 0);
console.log({}.toString.call(array).slice(8, -1)); //Array

var result = array.filter(function(item){return item.value.length > 5});

for(var i in result)
  console.log(result[i].value); //credit, confidence
<input type="text" value="trust"><br><br>
<input type="text" value="credit"><br><br>
<input type="text" value="confidence">

But if you want to iterate over the DOM node list easy only, then you do not need to convert a NodeList to an Array. It's possible to loop over the items in a NodeList using:

var nodeList = document.querySelectorAll('input');
// Calling nodeList.item(i) isn't necessary in JavaScript
for(var i = 0; i < nodeList.length; i++)
    console.log(nodeList[i].value); //trust, credit, confidence
<input type="text" value="trust"><br><br>
<input type="text" value="credit"><br><br>
<input type="text" value="confidence">

Don't be tempted to use for...in or for each...in to enumerate the items in the list, since that will also enumerate the length and item properties of the NodeList and cause errors if your script assumes it only has to deal with element objects. Also, for..in is not guaranteed to visit the properties in any particular order. for...of loops will loop over NodeList objects correctly.

See too:

Answer
var arr = new Array();
var x= ... get your nodes;

for (i=0;i<x.length;i++)
{
  if (x.item(i).nodeType==1)
  {
    arr.push(x.item(i));
  }
}

This should work, cross browser and get you all the "element" nodes.

Tags

Recent Questions

Top Questions

Home Tags Terms of Service Privacy Policy DMCA Contact Us Javascript

©2020 All rights reserved.