How to convert a DOM node list to an array in Javascript?
JavascriptHtmlDomJavascript Problem Overview
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?
Javascript Solutions
Solution 1 - Javascript
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
Solution 2 - Javascript
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.
Solution 3 - Javascript
spread (ES2015), it's as easy as: [...document.querySelectorAll('p')]
Using (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);
Solution 4 - Javascript
Use this simple trick
<Your array> = [].map.call(<Your dom array>, function(el) {
return el;
})
Solution 5 - Javascript
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).
Solution 6 - Javascript
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:
Solution 7 - Javascript
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.