360

Previously answered questions here said that this was the fastest way:

//nl is a NodeList
var arr = Array.prototype.slice.call(nl);

In benchmarking on my browser I have found that it is more than 3 times slower than this:

var arr = [];
for(var i = 0, n; n = nl[i]; ++i) arr.push(n);

They both produce the same output, but I find it hard to believe that my second version is the fastest possible way, especially since people have said otherwise here.

Is this a quirk in my browser (Chromium 6)? Or is there a faster way?

Elijah Mock
  • 587
  • 8
  • 21
jairajs89
  • 4,495
  • 3
  • 18
  • 14
  • 2
    `arr[arr.length] = nl[i];` may be faster than `arr.push(nl[i]);` since it avoids a function call. – Luc125 Nov 13 '11 at 13:16
  • 9
    This jsPerf page is keeping track of all the answers on this page: http://jsperf.com/nodelist-to-array/27 – pilau May 17 '13 at 19:32
  • Please note that the "EDIT2: I found a faster way" is 92% slower on IE8. – Camilo Martin Jun 14 '13 at 23:27
  • 2
    Since you know already know how many nodes you have: `var i = nl.length, arr = new Array(i); for(; i--; arr[i] = nl[i]);` – mems Oct 30 '14 at 15:37
  • @Luc125 It depends on the browser, since push implementation may be optimized, I'm thinking about chrome because v8 is good with this kind of stuff. – axelduch Dec 25 '14 at 18:06

15 Answers15

359

With ES6, we now have a simple way to create an Array from a NodeList: the Array.from() function.

// nl is a NodeList
let myArray = Array.from(nl)
Community
  • 1
  • 1
webdif
  • 6,341
  • 3
  • 18
  • 15
  • how does this new ES6 method compare in speed to the others that have been mentioned above? – user5508297 Jul 19 '16 at 21:45
  • 12
    @user5508297 Better than the slice call trick. Slower than the for loops, but that's not exactly we may want to have an array without traversing it. And the syntax is beautiful, simple and easy to remember! – webdif Aug 03 '16 at 15:07
  • 7
    Nice thing about Array.from is that you can use map argument: `console.log(Array.from([1, 2, 3], x => x + x)); // expected output: Array [2, 4, 6]` – honzajde Jan 06 '20 at 08:36
253

2021 update: nodeList.forEach() is now standard and supported in all current browsers (around 95% on both desktop & mobile).

So you can simply do:

document.querySelectorAll('img').forEach(highlight);

Other cases

If you for some reason want to convert it to an array, not just iterate over it - which is a completely relevant use-case - you can use [...destructuring] or Array.from since ES6

let array1 = [...mySetOfElements];
// or
let array2 = Array.from(mySetOfElements);

This also works for other array-like structures that aren't NodeLists

  • HTMLCollection returned by e.g. document.getElementsByTagName
  • objects with a length property and indexed elements
  • iterable objects (objects such as Map and Set)



Outdated 2010 Answer

The second one tends to be faster in some browsers, but the main point is that you have to use it because the first one is just not cross-browser. Even though The Times They Are a-Changin'

@kangax (IE 9 preview)

Array.prototype.slice can now convert certain host objects (e.g. NodeList’s) to arrays — something that majority of modern browsers have been able to do for quite a while.

Example:

Array.prototype.slice.call(document.childNodes);
Udo E.
  • 2,665
  • 2
  • 21
  • 33
gblazex
  • 49,155
  • 12
  • 98
  • 91
  • ??? Both are cross-browser compatible -- Javascript (at least if it claims to be compatible with the ECMAscript spec) is Javascript; Array, prototype, slice, and call are all features of the core language + object types. – Jason S Jul 07 '10 at 23:29
  • 6
    but they cannot be used on NodeLists in IE (I know it sucks, but hey see my update) – gblazex Jul 07 '10 at 23:32
  • 9
    because NodeLists are not part of the language, they are part of the DOM API, which is known to be buggy/unpredictable especially in IE – gblazex Jul 07 '10 at 23:36
  • 3
    Array.prototype.slice is not cross browser, if you take IE8 in account. – Lajos Mészáros Sep 01 '15 at 10:19
  • 1
    Yes, that's what my answer was basically about :) Though it was more relevant in 2010 than in today (2015). – gblazex Sep 18 '15 at 09:15
  • 1
    This can be shortened to `[].slice.call(document.childNodes)` :) ohhh happy days – George Jul 27 '18 at 13:56
121

Here's a new cool way to do it using the ES6 spread operator:

let arr = [...nl];
Alexander Olsson
  • 1,908
  • 1
  • 15
  • 24
27

In ES6 you can either use:

  • Array.from

    let array = Array.from(nodelist)

  • Spread operator

    let array = [...nodelist]

Community
  • 1
  • 1
Isabella
  • 394
  • 5
  • 13
19

Some optimizations:

  • save the NodeList's length in a variable
  • explicitly set the new array's length before setting.
  • access the indices, rather than pushing or unshifting.

Code (jsPerf):

var arr = [];
for (var i = 0, ref = arr.length = nl.length; i < ref; i++) {
 arr[i] = nl[i];
}
Thai
  • 10,746
  • 2
  • 45
  • 57
  • 1
    You can shave a *little* more time by using Array(length) rather than creating the array and then separately sizing it. If you then use const to declare the array and its length, with a let inside the loop, it ends up about 1.5% faster than the above method: const a = Array(nl.length), c = a.length; for (let b = 0; b < c; b++) { a[b] = nl[b]; } see https://jsperf.com/nodelist-to-array/93 – BrianFreud Apr 27 '19 at 01:13
15

The most fast and cross browser is

for(var i=-1,l=nl.length;++i!==l;arr[i]=nl[i]);

As I compared in

http://jsbin.com/oqeda/98/edit

*Thanks @CMS for the idea!

Chromium (Similar to Google Chrome) Firefox Opera

Boaz
  • 19,892
  • 8
  • 62
  • 70
Felipe Buccioni
  • 19,109
  • 2
  • 28
  • 28
15

The results will completely depend on the browser, to give an objective verdict, we have to make some performance tests, here are some results, you can run them here:

Chrome 6:

Firefox 3.6:

Firefox 4.0b2:

Safari 5:

IE9 Platform Preview 3:

Community
  • 1
  • 1
Christian C. Salvadó
  • 807,428
  • 183
  • 922
  • 838
  • 1
    I wonder how the reverse for loop holds up against these... `for (var i=o.length; i--;)` ... did the 'for loop' in these tests reevaluate the length property on every iteration? – Dagg Nabbit Jul 08 '10 at 01:49
10

Assuming nodeList = document.querySelectorAll("div"), this is a concise form of converting nodelist to array.

var nodeArray = [].slice.call(nodeList);

See me use it here.

Udo E.
  • 2,665
  • 2
  • 21
  • 33
7
NodeList.prototype.forEach = Array.prototype.forEach;

Now you can do document.querySelectorAll('div').forEach(function()...)

C B
  • 12,482
  • 5
  • 36
  • 48
  • Good idea, thanks @John! However, `NodeList` isn't working but `Object` is: `Object.prototype.forEach = Array.prototype.forEach; document.getElementsByTagName("img").forEach(function(img) { alert(img.src); });` – Ian Campbell Jan 14 '15 at 22:34
  • 3
    Don't use Object.prototype: it breaks JQuery and a ton of things like dictionary literal syntax. – Nate Symer Apr 14 '15 at 13:59
  • Sure, avoid to extend native built-in functions. – roland Jan 08 '16 at 17:26
5

faster and shorter :

// nl is the nodelist
var a=[], l=nl.length>>>0;
for( ; l--; a[l]=nl[l] );
Rory McCrossan
  • 331,213
  • 40
  • 305
  • 339
anonymous
  • 51
  • 1
  • 1
  • 3
    Why the `>>>0`? And why not put the assignments on the first part of the for loop? – Camilo Martin Jun 16 '13 at 10:00
  • 5
    Also, this is buggy. When `l` is `0`, the loop will end, therefore the `0`th element will not be copied (remeber there's an element at index `0`) – Camilo Martin Jun 16 '13 at 10:04
  • 1
    Love this answer, but... Anyone who's wondering: the `>>>` *may* not be necessary here but is used to guarantee the nodelist's length adheres to array spec; it ensures that it is an unsigned 32-bit integer. Check it out here http://www.ecma-international.org/ecma-262/5.1/#sec-15.4 If you like unreadable code, use this method with @CamiloMartin's suggestions! – Todd Dec 02 '14 at 08:45
  • In reply to @CamiloMartin - It's risky to put `var` inside the first part of a `for` loop because of 'variable hoisting'. The declaration of `var` will be 'hoisted' to the top of the function, even if the `var` line appears somewhere lower down, and this can cause side effects that are not obvious from the code sequence. For example some code in the same function occurring **before** the for loop might **depend** on `a` and `l` being undeclared. Therefore for greater predicability, declare your vars at the top of the function (or if on ES6, use `const` or `let` instead, which don't hoist). – brennanyoung Apr 08 '19 at 13:05
3

This is the function I use in my JS:

function toArray(nl) {
    for(var a=[], l=nl.length; l--; a[l]=nl[l]);
    return a;
}
Web_Designer
  • 72,308
  • 93
  • 206
  • 262
3

Check out this blog post here that talks about the same thing. From what I gather, the extra time might have to do with walking up the scope chain.

Vivin Paliath
  • 94,126
  • 40
  • 223
  • 295
  • Interesting. I just did some similar tests now and Firefox 3.6.3 shows no increase in speed doing it either way, while Opera 10.6 has a 20% increase and Chrome 6 has a 230% (!) increase doing it the manual iterate-push way. – jairajs89 Jul 07 '10 at 23:23
  • @jairajs89 quite strange. It appears that the `Array.prototype.slice` is browser-dependant. I wonder what algorithm each of the browsers are using. – Vivin Paliath Jul 07 '10 at 23:43
2

Here are charts updated as of the date of this posting ("unknown platform" chart is Internet Explorer 11.15.16299.0):

Safari 11.1.2 Firefox 61.0 Chrome 68.0.3440.75 Internet Explorer 11.15.16299.0

From these results, it seems that the preallocate 1 method is the safest cross-browser bet.

TomSlick
  • 717
  • 5
  • 21
2

One liner here, I am not sure if it is safe, but it works for me, it is overwriting the nodelist variable with an array, because I no longer use the nodelist, as I converted it to an array. I find this solution cleaner as it uses just the one variable.

this.openButtons = [...this.openButtons]

jakdud002
  • 21
  • 1
0

The simplest way:

Array.from(document.querySelectorAll('.back-top'))
Rahul Daksh
  • 212
  • 1
  • 7
  • 2
    If all you want is to iterate over the NodeList, there's no need to convert it to an array, as noted in [this anser](https://stackoverflow.com/a/3199627/111794). In any case, `Array.from` is also described in the aforementioned answer; thus this answer adds nothing new. – Zev Spitz May 30 '22 at 16:46