130

The DOM method document.querySelectorAll() (and a few others) return a NodeList.

To operate on the list, e.g. using forEach(), the NodeList must first be converted to an Array.

What's the best way to convert the NodeList to an Array?

mikemaccana
  • 110,530
  • 99
  • 389
  • 494
cc young
  • 18,939
  • 31
  • 90
  • 148

12 Answers12

142

With ES6 you can simply do:

const spanList = [...document.querySelectorAll("span")];
Freezystem
  • 4,494
  • 1
  • 32
  • 35
  • 10
    This gives me `Type 'NodeListOf' must have a '[Symbol.iterator]()' method that returns an iterator.ts(2488)` – callback Aug 09 '20 at 19:57
  • 1
    Hi @callback, this seems like a TypeScript related error. You may have chosen to target es6 compilation without adding "es6" in the lib array of your tsconfig file. Regards – Freezystem Aug 10 '20 at 11:31
  • 1
    Hi @Freezystem, you are right! I am targetting es2015. Thanks! – callback Aug 10 '20 at 11:34
  • @callback ES6 and ES2015 are the same thing – DarkNeuron Oct 14 '20 at 11:40
  • 3
    Just a heads up that **you don't need to convert a nodeList into an array to use forEach()** - see my answer below. – mikemaccana Apr 15 '21 at 17:24
  • @callback I solved this by adding `"DOM.Iterable"` to the libs in the `tsconfig.json` file. It adds the `[Symbol.iterator]()` definition to many of the DOM types. See [this definition file](https://github.com/microsoft/TypeScript/blob/main/src/lib/dom.iterable.d.ts). – Jean-Philippe Pellet Apr 13 '23 at 09:00
87

With ES6 you can use Array.from(myNodeList). Then use your favourite array method.

var myNodeList = document.querySelectorAll('.my-selector');

// ALT 1
Array.from(myNodeList).forEach(function(el) {
  console.log(el);
});

Use an ES6 shim to make this work in older browsers too.


If you are using a transpiler (for example Babel) there are two more alternatives:

var myNodeList = document.querySelectorAll('.my-selector');

// ALT 2
for (var el of myNodeList) {
  el.classList.add('active'); // or some other action
}

// ALT 3
[...myNodeList].forEach((el) => {
  console.log(el);
});
sandstrom
  • 14,554
  • 7
  • 65
  • 62
  • isn't also true under es6 that a nodeList supplies an iterator? – cc young Nov 05 '15 at 10:04
  • 4
    @ccyoung but the iterator doesn't work in non-compliant ES6 browsers because you can't shim Symbol object so it's better to use `Array.from(myNodeList)` because it can be shimmed. – Roc Mar 11 '16 at 12:10
  • so i have this issue where Array.from(el.childNodes) does not return the first node as part of the array. – zinoadidi Oct 28 '18 at 07:44
54

You can convert it to an array by using the slice method from the Array prototype:

var elList = document.querySelectorAll('.viewcount');
elList = Array.prototype.slice.call(elList, 0);

Furthermore, if all you need is forEach, you can invoke that from the Array prototype, without coercing it to an array first:

var elList = document.querySelectorAll('.viewcount');
Array.prototype.forEach.call(elList, function(el) {
    console.log(el);
});

In ES6, you can use the new Array.from function to convert it to an array:

Array.from(elList).forEach(function(el) {
    console.log(el);
});

This is currently only in bleeding edge browsers, but if you're using a polyfill service you will have access to this function across the board.


If you're using an ES6 transpiler, you can even use a for..of loop instead:

for (var element of document.querySelectorAll('.some .elements')) {
  // use element here
}
Joseph Silber
  • 214,931
  • 59
  • 362
  • 292
  • thanks. under newer javascript thought/hoping there was a more succinct coercion. – cc young Sep 18 '11 at 05:51
  • 6
    @cc young - Do note, the reason I'm using `Array.prototype.forEach` instead of `[].forEach`, is because the latter creates a new Array object, which is totally unnecessary. – Joseph Silber Sep 18 '11 at 23:01
  • @JosephSilber ahh thank you - that is the new array that's created is the empty `[]`? My thinking is that it would get garbage collected and the memory impact is negligible, can anyone comment on this? – Daniel Sokolowski Apr 25 '15 at 16:21
  • @Daniel this is true, but there's still the computation of creating and destroying the array. – Brett Oct 10 '15 at 00:49
23

Why convert? - just call function of Array directly on element collection ;)

[].forEach.call( $('a'), function( v, i) {
    // do something
});

assuming $ is your alias for querySelectorAll, of course


edit: ES6 allows for even shorter syntax [...$('a')] (works in Firefox only, as of May 2014)

c69
  • 19,951
  • 7
  • 52
  • 82
  • Assuming `$` is `querySelectorAll`. – c69 Sep 18 '11 at 05:49
  • 3
    Your answer implies jQuery usage. If that's the case, this tomfoolery is completely unnecessary, thanks to [`.each()`](http://api.jquery.com/each). – Matt Ball Sep 18 '11 at 05:49
  • 2
    lol, why ? nothing forbids you from making aliases like `function $ ( s ) { return document.querySelectorAll(s); }`. – c69 Sep 18 '11 at 05:51
  • The answer mentioned no such alias. – Matt Ball Sep 18 '11 at 05:52
  • most succinct, despite your jQuery - think this is what I was looking for – cc young Sep 18 '11 at 06:00
  • forEach method is not a standard method from Array, Array.prototype.slice is – wukong Sep 18 '11 at 06:28
  • 5
    If you're going to use jQuery, then the more succint solution is: `$('a').each(function(i, v) {...});` – jfriend00 Sep 18 '11 at 06:30
  • 2
    offtopic: _EcmaScript 5 is a standard for a year already, all current-generation browsers support the new methods of Arrays, and question was specificly about using those methods on NodeList aka Element collection._ – c69 Sep 18 '11 at 06:34
16

2020 update: nodeList.forEach() is now an official standard and supported in all current browsers.

Older browsers can use the polyfill below.

To operate on the list in javascript, e.g. using forEach(), the NodeList must be converted to an Array.

That's not true. .forEach() works in current browsers. If it's missing, you can use a polyfill to add .forEach() from Array to NodeList and it works fine:

if ( ! NodeList.prototype.forEach ) {
  NodeList.prototype.forEach = Array.prototype.forEach;
}

You can now run:

myNodeList.forEach(function(node){...})

To iterate over NodeLists just like Arrays.

This produces much shorter and cleaner code than .call() everywhere.

mikemaccana
  • 110,530
  • 99
  • 389
  • 494
  • 1
    This answer was exactly what I needed and these 3 lines saved a lot of time. All the other answers would have required changing a bunch of code and I'm not understanding why. – Scribblemacher Oct 15 '18 at 18:00
  • downvotes were probably because monkey patching built-in prototypes is considered bad practice – DuBistKomisch Oct 14 '20 at 04:04
  • 3
    @DuBistKomisch This is a polyfill, only applied if the standard NodeList.foreach() doesn't exist. – mikemaccana Oct 14 '20 at 09:27
  • 3
    ah my bad, didn't realise they actually added `forEach` specifically, I came here looking for `filter` – DuBistKomisch Oct 15 '20 at 22:29
  • @DuBistKomisch you can use the same technique for `filter`, but you may wish to give it an unofficial name `NodeList.prototype.dbkFilter` or similar if you're worried about a future standard using a different implementation . – mikemaccana Oct 27 '20 at 17:02
  • For millions of people, IE11 is a current browser. – Mike Godin Jan 24 '22 at 15:41
10

Does it have to be forEach? You could simply use a for loop to iterate over the list:

for (var i = 0; i < elementList.length; i++) {
    doSomethingWith(elementlist.item(i));
}
Timo Tijhof
  • 10,032
  • 6
  • 34
  • 48
nfechner
  • 17,295
  • 7
  • 45
  • 64
  • 2
    +1 for going with the simple solution that doesn't add unnecessary array conversions. FYI, Instead of `elementList.item(i)`, you could just use `elementList[i]`. – jfriend00 Sep 18 '11 at 05:54
  • 4
    personally I find `forEach()` a better programming style and less verbose - ymmv – cc young Sep 18 '11 at 06:05
  • @cc young: Actually, I agree with you. Except in cases like this, where I would need to run a conversion just so I can use my favorite pattern. That makes it clunky and looks like: "When all you have is a hammer, everything starts to look like a nail." – nfechner Sep 18 '11 at 06:11
  • And here is a nuttier way :) `for (var oElement, i = 0; oElement = aMenuItemsElements; i++ { console.log(oElement); }` – Daniel Sokolowski Apr 24 '15 at 02:26
  • Problem here is that you can’t nest another `for (var i…)` loop because the for loop doesn’t create its own scope (like it does in C/C++ now). And then the `i` get mixed up. – Jens Jun 21 '19 at 09:52
5

Well, this works for me too:

const elements = Object.values( document.querySelector(your selector here) )

Object.values() returns Array of values of given object. NodeList is object, as is everything in JS.

https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/values

But it's not compatible with IE, so i guess Array.prototype.*array_method*.call(yourNodeList) is the best option. With this you can invoke any array method on your NodeList

3

I use the following because I think it's easiest to read:

const elements = document.getElementsByClassName('element');
[...elements].forEach((element) => {
   // code
});

brad
  • 1,407
  • 19
  • 33
2

Assuming elems is a nodeList:

var elems = document.querySelectorAll('select option:checked');

then it can be turned into an array as follows:

var values = [].map.call(elems, function(obj) {
  return obj.value;
});

Reference: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/map#Example:_using_map_generically_querySelectorAll

Per Quested Aronsson
  • 11,380
  • 8
  • 54
  • 76
2

ES6 allows cool ways like var nodeArray = Array.from(nodeList) but my favorite one is the new spread operator.

var nodeArray = Array(...nodeList);
Redu
  • 25,060
  • 6
  • 56
  • 76
2

That worked with me in ES6

lets assume you have nodelist like that

<ul>
  <li data-time="5:17">Flexbox video</li>
  <li data-time="8:22">Flexbox video</li>
  <li data-time="3:24">Redux video</li>
  <li data-time="5:17">Flexbox video</li>
  <li data-time="7:17">Flexbox video</li>
  <li data-time="4:17">Flexbox video</li>
  <li data-time="2:17">Redux video</li>
  <li data-time="7:17">Flexbox video</li>
  <li data-time="9:54">Flexbox video</li>
  <li data-time="5:53">Flexbox video</li>
  <li data-time="7:32">Flexbox video</li>
  <li data-time="2:47">Redux video</li>
  <li data-time="9:17">Flexbox video</li>

</ul>


const items = Array.from(document.querySelectorAll('[data-time]'));

console.log(items);
Amr.Ayoub
  • 718
  • 9
  • 20
0

TypeScript version:

const allDayElements: Element[] = [].slice.call(document.querySelectorAll('span'));
Mohammad Dayyan
  • 21,578
  • 41
  • 164
  • 232