637

Is there a more efficient way to convert an HTMLCollection to an Array, other than iterating through the contents of said collection and manually pushing each item into an array?

Tom
  • 15,527
  • 5
  • 48
  • 62
  • 14
    What is meant by "efficient"? If best performing, a *for* loop is generally faster than *Array.prototype.slice*. A loop also works in a wider variety of browsers (i.e. all), so by those criteria it *is* the "most efficient way". And it's very little code: `for (var a=[], i=collection.length; i;) a[--i] = collection[i];` so not much of a "con" there :-) – RobG Jan 28 '15 at 20:18
  • @RobG Thank you - I'd give you +59k if I could! ;-) – Slashback Jan 03 '16 at 03:15
  • 1
    Looking at [*current browser performance*](http://jsperf.com/slice-vs-loop-2016), *slice* has mostly caught up with loops in terms of performance, except in Chrome. Using a larger number of elements and slight optimisation of the loop, the [*results are almost identical*](http://jsperf.com/slice-vs-loop/11), except in Chrome where a loop is very much faster. – RobG Jan 04 '16 at 23:17
  • I created a jsperf test that looks at both methods that @harpo mentioned as well as a jquery test for performance. I've found jquery is slightly slower than both javascript methods and top performance varies between the js test cases. Chrome 59.0.3071 / Mac OS X 10.12.5 prefers using `Array.prototype.slice.call` and Brave (based on Chrome 59.0.3071) has virtually no difference between the two javascript tests over multiple runs. See https://jsperf.com/htmlcollection-array-vs-jquery-children – NuclearPeon Jul 11 '17 at 23:31
  • 1
    http://jsben.ch/h2IFA => performance test for the most common ways to do this – EscapeNetscape Feb 13 '20 at 15:24
  • If my HTMLCollection includes dynamic links, how do I convert into an array while maintaining the links? When I convert into an array using a for loop, it comes in as text only. I lose the links because of innerHTML – bfoley_teamug Jul 07 '20 at 18:51

10 Answers10

1073
var arr = Array.prototype.slice.call( htmlCollection )

will have the same effect using "native" code.

Edit

Since this gets a lot of views, note (per @oriol's comment) that the following more concise expression is effectively equivalent:

var arr = [].slice.call(htmlCollection);

But note per @JussiR's comment, that unlike the "verbose" form, it does create an empty, unused, and indeed unusable array instance in the process. What compilers do about this is outside the programmer's ken.

Edit

Since ECMAScript 2015 (ES 6) there is also Array.from:

var arr = Array.from(htmlCollection);

Edit

ECMAScript 2015 also provides the spread operator, which is functionally equivalent to Array.from (although note that Array.from supports a mapping function as the second argument).

var arr = [...htmlCollection];

I've confirmed that both of the above work on NodeList.

A performance comparison for the mentioned methods: http://jsben.ch/h2IFA

EscapeNetscape
  • 2,892
  • 1
  • 33
  • 32
harpo
  • 41,820
  • 13
  • 96
  • 131
  • 35
    The shortcut `[].slice.call(htmlCollection)` also works. – Oriol Apr 24 '14 at 19:04
  • 1
    @ChrisNielsen Yes I was misinformed on that. Sorry for spreading that around. I didn't realize I'd stated that here as well. Deleted the comment to avoid confusion but for context I had read (or misread) somewhere that slicing an HTMLCollection made it behave like both an array and a collection. Totally incorrect. – Erik Reppen Jun 10 '14 at 19:12
  • 1
    Fwiw, the content of Oriol's comment was also mentioned in [@AUTO 's answer, below](http://stackoverflow.com/a/22676083/1028230) about a month earlier, if you want to spread the karma around a little. – ruffin Jun 05 '15 at 19:24
  • The MDN docs for Array.prototype.slice have a nice explanation of when to use this type of conversion and a nice polyfill for Internet Explorer < 9. https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/slice – Chaoix Oct 20 '15 at 17:08
  • 1
    var arr = [].slice.call(htmlCollection); fails in IE 11, but Array.prototype still works – Nico Jan 29 '17 at 14:20
  • 3
    The [].slice shortcut is not equivalent since it also creates unused empty array instance. Not sure if compilers are able to optimize it away, though. – JussiR Feb 08 '17 at 14:42
  • 5
    `Array.from`, i.e. `from`, is not supported by IE11. – Frank Conijn - Support Ukraine Jun 17 '18 at 12:07
  • 1
    How does Array.prototype.slice.call( htmlCollection) work? Can anyone here explain? – forethought Jan 27 '19 at 17:55
  • @forethought, `Array.prototype.slice` is not normally used this way. Instead, you would call it as a method on an array like `[3, 5, 7].slice()`. That creates and returns a new Array based on the indexes passed to `slice`. When you pass no arguments, it copies the whole array. HTML collections don't have `Array` in their prototype chain, so you can't use the `slice` method on references to them. However, if you use `call()` (which is on `Function.prototype`) to invoke `Array.prototype.slice` on an array-like object, it accomplishes the same thing. Hope this helps! – harpo Jan 28 '19 at 02:04
  • 1
    the spread operator did not work, at least not like this: `[...htmlColl].forEach((i)=>{//do})` – oldboy Jul 16 '19 at 06:05
  • 1
    Spread operator is not working. Type 'HTMLCollection' is not an array type – Davide Feb 25 '20 at 14:23
  • 5
    Typescript disallow spread operator because htmlCollection doesn't have `[Symbol.iterator]()` method. – Yukulélé Nov 13 '21 at 08:14
  • Typescript can't disallow what is unknown: `[... htmlCollection as unknown as Element[]]` – SmujMaiku Feb 03 '22 at 22:45
  • I get `[]` as result when converting a HTMLColl to Array. The Collection though has elements and can be consolelogged as a whole, but not each element. – Timo Jul 11 '22 at 13:56
  • Gist: Array.prototype === [] – Rahul Daksh Jan 04 '23 at 10:54
133

not sure if this is the most efficient, but a concise ES6 syntax might be:

let arry = [...htmlCollection] 

Edit: Another one, from Chris_F comment:

let arry = Array.from(htmlCollection)
mido
  • 24,198
  • 15
  • 92
  • 117
26

I saw a more concise method of getting Array.prototype methods in general that works just as well. Converting an HTMLCollection object into an Array object is demonstrated below:

[].slice.call( yourHTMLCollectionObject );

And, as mentioned in the comments, for old browsers such as IE7 and earlier, you simply have to use a compatibility function, like:

function toArray(x) {
    for(var i = 0, a = []; i < x.length; i++)
        a.push(x[i]);

    return a
}

I know this is an old question, but I felt the accepted answer was a little incomplete; so I thought I'd throw this out there FWIW.

Henk van Boeijen
  • 7,357
  • 6
  • 32
  • 42
Codesmith
  • 5,779
  • 5
  • 38
  • 50
7

For a cross browser implementation I'd sugguest you look at prototype.js $A function

copyed from 1.6.1:

function $A(iterable) {
  if (!iterable) return [];
  if ('toArray' in Object(iterable)) return iterable.toArray();
  var length = iterable.length || 0, results = new Array(length);
  while (length--) results[length] = iterable[length];
  return results;
}

It doesn't use Array.prototype.slice probably because it isn't available on every browser. I'm afraid the performance is pretty bad as there a the fall back is a javascript loop over the iterable.

Gareth Davis
  • 27,701
  • 12
  • 73
  • 106
  • 2
    The OP asked for an other way than "iterating through the contents of said collection and manually pushing each item into an array", but that's precisely what the `$A` function does most of the time. – Luc125 Nov 13 '11 at 13:12
  • 1
    I think the point I was trying to make is that there isn't a nice way to do it, the prototype.js code shows that you can look for a 'toArray' method but failing that iteration the safest route – Gareth Davis Nov 13 '11 at 19:45
  • 1
    This will create new, undefined members in sparse arrays. There should be a *hasOwnProperty* test before the assignment. – RobG Jan 04 '16 at 09:49
6

This works in all browsers including earlier IE versions.

var arr = [];
[].push.apply(arr, htmlCollection);

Since jsperf is still down at the moment, here is a jsfiddle that compares the performance of different methods. https://jsfiddle.net/qw9qf48j/

Nicholas
  • 768
  • 6
  • 16
5

To convert array-like to array in efficient way we can make use of the jQuery makeArray :

makeArray: Convert an array-like object into a true JavaScript array.

Usage:

var domArray = jQuery.makeArray(htmlCollection);

A little extra:

If you do not want to keep reference to the array object (most of the time HTMLCollections are dynamically changes so its better to copy them into another array, This example pay close attention to performance:

var domDataLength = domData.length //Better performance, no need to calculate every iteration the domArray length
var resultArray = new Array(domDataLength) // Since we know the length its improves the performance to declare the result array from the beginning.

for (var i = 0 ; i < domDataLength ; i++) {
    resultArray[i] = domArray[i]; //Since we already declared the resultArray we can not make use of the more expensive push method.
}

What is array-like?

HTMLCollection is an "array-like" object, the array-like objects are similar to array's object but missing a lot of its functionally definition:

Array-like objects look like arrays. They have various numbered elements and a length property. But that’s where the similarity stops. Array-like objects do not have any of Array’s functions, and for-in loops don’t even work!

Shahar Shokrani
  • 7,598
  • 9
  • 48
  • 91
5

I suppose that calling Array.prototype functions on instances of HTMLCollection is a much better option than converting collections to arrays (e.g.,[...collection] or Array.from(collection)), because in the latter case a collection is unnecessarily implicitly iterated and a new array object is created, and this eats up additional resources. Array.prototype iterating functions can be safely called upon objects with consecutive numeric keys starting from [0] and a length property with a valid number value of such keys' quantity (including, e.g., instances of HTMLCollection and FileList), so it's a reliable way. Also, if there is a frequent need in such operations, an empty array [] can be used for quick access to Array.prototype functions. A runnable example:

alert(
    Array.prototype.reduce.call(
        document.querySelector('ol').children,
        (acc, { textContent }, i) => `${acc}${i + 1}) ${textContent}` + `\n`,
        '',
    ),
);
<ol>
    <li>foo</li>
    <li>bar</li>
    <li>bat</li>
    <li>baz</li>
</ol>
Roman Karagodin
  • 740
  • 2
  • 11
  • 16
3

This is my personal solution, based on the information here (this thread):

var Divs = new Array();    
var Elemns = document.getElementsByClassName("divisao");
    try {
        Divs = Elemns.prototype.slice.call(Elemns);
    } catch(e) {
        Divs = $A(Elemns);
    }

Where $A was described by Gareth Davis in his post:

function $A(iterable) {
  if (!iterable) return [];
  if ('toArray' in Object(iterable)) return iterable.toArray();
  var length = iterable.length || 0, results = new Array(length);
  while (length--) results[length] = iterable[length];
  return results;
}

If browser supports the best way, ok, otherwise will use the cross browser.

Gustavo
  • 1,673
  • 4
  • 24
  • 39
  • In general, I don't expect try/catch to be an efficient way to manage control flow. You can check if the function exists first, then run either one or the other a bit cheaper. – Patrick Apr 26 '15 at 17:52
  • 2
    As with Gareth Davis' answer, this creates new, undefined members in sparse arrays, so `[,,]` becomes `[undefined, undefined]`. – RobG Jan 04 '16 at 09:50
  • I didn't get this kind of trouble yet. It seams a 3 elements collection results in an array with 2 elements. As for empty become undefined, it's a bit of JavaScript limitations, I gess you were expecting null instead of undefined, right? – Gustavo Mar 15 '16 at 12:36
3

Sometimes, Even You have written code the correct way, But still it doesn't work properly.

var allbuttons = document.getElementsByTagName("button");
console.log(allbuttons);

var copyAllButtons = [];
for (let i = 0; i < allbuttons.length; i++) {
  copyAllButtons.push(allbuttons[i]);
}
console.log(copyAllButtons);

you get empty array. Like, This

HTMLCollection []
[]

Console_javascript

For Solving this problem, You have to add link of javascript file after body tag in html file.

<script src="./script.js"></script>

As you can see below, html_file

Final Output

HTMLCollection(6) [button.btn.btn-dark.click-me, button.btn.btn-dark.reset, button#b, button#b, button#b, button#b, b: button#b]
(6) [button.btn.btn-dark.click-me, button.btn.btn-dark.reset, button#b, button#b, button#b, button#b]
Avi
  • 31
  • 4
0

I'm not sure about efficiency but, from a purely aesthetic point of view, I find this pleasing.

HTMLCollection.prototype.toArray = function() { return Array.from(this); }

now you can use it like this.

document.getElementsByClassName('tax-status').toArray().forEach(console.log);
hanan
  • 1,768
  • 1
  • 14
  • 19