Fastest way to convert JavaScript NodeList to Array?
Asked Answered
H

15

381

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?

Hutcherson answered 7/7, 2010 at 23:4 Comment(5)
arr[arr.length] = nl[i]; may be faster than arr.push(nl[i]); since it avoids a function call.Wortman
This jsPerf page is keeping track of all the answers on this page: jsperf.com/nodelist-to-array/27Electrotype
Please note that the "EDIT2: I found a faster way" is 92% slower on IE8.Camelopardus
Since you know already know how many nodes you have: var i = nl.length, arr = new Array(i); for(; i--; arr[i] = nl[i]);Deist
@Wortman 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.Billat
Z
264

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);
Zehe answered 7/7, 2010 at 23:17 Comment(6)
??? 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.Queue
but they cannot be used on NodeLists in IE (I know it sucks, but hey see my update)Zehe
because NodeLists are not part of the language, they are part of the DOM API, which is known to be buggy/unpredictable especially in IEZehe
Array.prototype.slice is not cross browser, if you take IE8 in account.Choirboy
Yes, that's what my answer was basically about :) Though it was more relevant in 2010 than in today (2015).Zehe
This can be shortened to [].slice.call(document.childNodes) :) ohhh happy daysLubricous
I
380

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)
Imposture answered 27/3, 2016 at 15:19 Comment(3)
how does this new ES6 method compare in speed to the others that have been mentioned above?Papilloma
@Papilloma 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!Imposture
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]Fossilize
Z
264

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);
Zehe answered 7/7, 2010 at 23:17 Comment(6)
??? 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.Queue
but they cannot be used on NodeLists in IE (I know it sucks, but hey see my update)Zehe
because NodeLists are not part of the language, they are part of the DOM API, which is known to be buggy/unpredictable especially in IEZehe
Array.prototype.slice is not cross browser, if you take IE8 in account.Choirboy
Yes, that's what my answer was basically about :) Though it was more relevant in 2010 than in today (2015).Zehe
This can be shortened to [].slice.call(document.childNodes) :) ohhh happy daysLubricous
T
122

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

let arr = [...nl];
Triplane answered 20/11, 2015 at 8:56 Comment(2)
Didn't work for me in typescript. ERROR TypeError: el.querySelectorAll(...).slice is not a functionPhthisis
Comes in 3rd fastest, behind the 3 unshift methods, using Chrome 71: jsperf.com/nodelist-to-array/90Almeida
T
27

In ES6 you can either use:

  • Array.from

    let array = Array.from(nodelist)

  • Spread operator

    let array = [...nodelist]

Thickness answered 26/2, 2019 at 11:2 Comment(1)
Doesn't add anything new what already has been written in previous answers.Byrne
V
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];
}
Viceregal answered 9/3, 2011 at 8:4 Comment(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 jsperf.com/nodelist-to-array/93Almeida
H
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:

Helsinki answered 7/7, 2010 at 23:31 Comment(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?Toplofty
M
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

Millennium answered 28/2, 2013 at 19:43 Comment(1)
the link seems to be wrong, should be 91, instead of 89 to include the test you mention. And 98 seems the most complete one.Situs
E
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.

Expedite answered 25/8, 2019 at 16:2 Comment(0)
B
7
NodeList.prototype.forEach = Array.prototype.forEach;

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

Bioclimatology answered 25/12, 2014 at 17:52 Comment(3)
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); });Blindstory
Don't use Object.prototype: it breaks JQuery and a ton of things like dictionary literal syntax.Tem
Sure, avoid to extend native built-in functions.Avocado
R
5

faster and shorter :

// nl is the nodelist
var a=[], l=nl.length>>>0;
for( ; l--; a[l]=nl[l] );
Roadstead answered 22/1, 2013 at 14:11 Comment(4)
Why the >>>0? And why not put the assignments on the first part of the for loop?Camelopardus
Also, this is buggy. When l is 0, the loop will end, therefore the 0th element will not be copied (remeber there's an element at index 0)Camelopardus
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 ecma-international.org/ecma-262/5.1/#sec-15.4 If you like unreadable code, use this method with @CamiloMartin's suggestions!Deliberation
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).Gualterio
K
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.

Katey answered 7/7, 2010 at 23:19 Comment(2)
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.Hutcherson
@Hutcherson quite strange. It appears that the Array.prototype.slice is browser-dependant. I wonder what algorithm each of the browsers are using.Katey
S
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;
}
Superego answered 8/3, 2013 at 3:21 Comment(0)
S
3

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]

Sommersommers answered 21/8, 2022 at 14:15 Comment(0)
I
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.

Imbecility answered 13/8, 2018 at 15:40 Comment(0)
S
0

The simplest way:

Array.from(document.querySelectorAll('.back-top'))
Sian answered 17/5, 2022 at 2:36 Comment(1)
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. In any case, Array.from is also described in the aforementioned answer; thus this answer adds nothing new.Sugden

© 2022 - 2024 — McMap. All rights reserved.