92

Is there some difference between using Array.from(document.querySelectorAll('div')) or [...document.querySelectorAll('div')]?

Here is a example:

let spreadDivArray = [...document.querySelectorAll('div')];
console.log(spreadDivArray);

let divArrayFrom = Array.from(document.querySelectorAll('div'));
console.log(divArrayFrom);

The console.log() will log the same result.

Is there any performance difference?

zcoop98
  • 2,590
  • 1
  • 18
  • 31
jotavejv
  • 1,936
  • 2
  • 16
  • 16
  • good thing with spread operator is that it supports `Object`. performance.. idk – Semi-Friends Nov 11 '16 at 12:40
  • To find out if there's any performance difference, run a benchmark. The results are likely to be quite different depending on whether you're in a native ES6 environment or transpiling to ES5. –  Nov 11 '16 at 12:46
  • 7
    The main difference is that `Array.from` works with array-like objects which don't implement the iterator protocol (i.e. `Symbol.iterator`). Even with ES6 and new browser specs, there are fewer and fewer of those. – nils Nov 11 '16 at 12:46
  • 5
    [`...` is not an operator!](http://stackoverflow.com/a/37152508/218196) – Felix Kling Nov 11 '16 at 15:56
  • In addition to performance, there may be different upper limits in array size that these can handle. On Chrome at least, the spread operator seems to throw a "Maximum call stack size exceeded" when used with very large arrays, while `Array.from()` works fine. – peterflynn May 13 '22 at 07:47

6 Answers6

89

Spread element or syntax (note that it's not an operator) works only with objects that are iterable (i.e. implement the @@iterator method). Array.from() works also on array-like objects which are not iterable (i.e. objects that have indexed elements and a length property).

See this example:

const arrayLikeObject = { 0: 'a', 1: 'b', length: 2 };

// This logs ['a', 'b']
console.log(Array.from(arrayLikeObject));

// This throws TypeError: arrayLikeObject[Symbol.iterator] is not a function
console.log([...arrayLikeObject]);

Also, if you just want to convert something to an array, I think it's better to use Array.from() because it's more readable. Spread elements are useful for example when you want to concatenate multiple arrays (['a', 'b', ...someArray, ...someOtherArray]).

zcoop98
  • 2,590
  • 1
  • 18
  • 31
Michał Perłakowski
  • 88,409
  • 26
  • 156
  • 177
  • 8
    While I agree that `Array.from()` is an extremely readable way to implement this, I feel that the spread element syntax `...arrayLikeObject` is just as readable for people (or more so). – qarthandso Aug 21 '17 at 00:32
  • 2
    Also note spread syntax (`...arrayLikeObject`) is much shorter. That can be a factor sometimes, though maybe it shouldn't be. – trysis Jun 06 '18 at 13:26
  • @qarthandso If we are spreading into the new (different) array, then I would agree. But if we need to duplicate an array (into exact same one), then Array.from looks more attractive and at least in some cases more readable, i.e when we need to pass a starting value of `Array.prototype.reduce` to be the array, on which we called it. – Eduard Sep 10 '18 at 12:32
  • 2
    Warning: number arguments such as `var i = 5; Array.from(i)` results in `[]` where as `var i = 5; [i]` results in `[5]` – Josh Stodola Aug 07 '19 at 16:08
12

Well, Array.from is a static method, i.e., a function whereas the spread syntax is part of the array literal syntax. You can pass functions around like data, you can invoke them once, several times or not at all. This isn't possible with the spread syntax, which is static in this regard.

Another difference, which @nils has already pointed out, is that Array.from also works with array-like objects, which don't implement the iterable protocol. spread on the other hand requires iterables.

  • 2
    "Array.from also works with array-like objects, which don't implement the iterable protocol" -- can you give an example of one such object? – mpen Aug 09 '17 at 00:39
7

The difference is that spread ... allows an array to be expanded, whereas Array.from() creates a new array directly.

In your example, [...x] creates a new array by using spread syntax to expand the existing array values into the array literal syntax– the brackets [] are what's actually doing the array-creation bit. Those brackets can be swapped out with any number of other syntax to achieve other results (e.g. spreading into function parameters foo(...x), into an object {...x}, using multiple spreads to concatenate arrays [...x, ...y], etc.).

.from() never expands upon anything, it always creates a new array based on the data provided.

zcoop98
  • 2,590
  • 1
  • 18
  • 31
James Donnelly
  • 126,410
  • 34
  • 208
  • 218
  • 20
    Well, array literals always create a *new* array as well… – Bergi Nov 11 '16 at 12:53
  • 4
    I'm not sure whether I just misunderstand your wording, but are you suggesting that the spread operator mutates the array instead of creating a new one? – Bergi Nov 11 '16 at 12:55
  • 4
    @Bergi well the spread operator doesn't *create* an array. The array creation in OP's example is done via the square brackets surrounding the spread operator. – James Donnelly Nov 11 '16 at 13:00
  • 4
    Ah, OK, I just wanted to be sure you meant the right thing. Maybe it would help if you said "expands the array literal" instead of "expands an array", since it doesn't operate on arbitrary arrays. – Bergi Nov 11 '16 at 13:03
  • 5
    **To clarify:** The `...foo` syntax just spreads (expands) all array values as if they were separate, comma-separated arguments. The `[]` around it is what CREATES a new array. So `[...foo]` will create a new array and populate it by spreading all array elements as if they were array constructor arguments and does a FULL COPY of every element. Whereas `Array.from(foo)` will CREATE a new array using the input variable, and is A LOT FASTER because it creates a SHALLOW COPY (this is FASTER). – Mitch McMabers May 01 '21 at 22:58
  • 3
    @MitchMcMabers they are both a shallow copy. It is _not_ true to say that spread does a "full copy." – peterflynn May 13 '22 at 07:45
3

If the input is iterable they do the exact same thing.

However, based on the benchmarks, the spread operator seems to perform better for a Set.

https://jsben.ch/5lKjg

let set = new Set();
for (let i = 0; i < 10000; i++) {
  set.add(Math.random());
}


let tArrayFrom = window.performance.now()

let arr = Array.from(set)

console.log("Array.from():", window.performance.now() - tArrayFrom + "ms")


// slightly faster in most of the runs:
let tSpread = window.performance.now()

let arr2 = [...set];

console.log("Spread syntax:", window.performance.now() - tSpread + "ms")
Amin Ya
  • 1,515
  • 1
  • 19
  • 30
2

Using Babel is a good way to see what's happening internally.

Heads up, though. Make sure latest is selected in Babel, as the default is wrong.

Using your example above, this is the output.

function _toConsumableArray(arr) { if (Array.isArray(arr)) { for (var i = 0, arr2 = Array(arr.length); i < arr.length; i++) { arr2[i] = arr[i]; } return arr2; } else { return Array.from(arr); } }

var spreadDivArray = [].concat(_toConsumableArray(document.querySelectorAll('div')));
console.log(spreadDivArray);

var divArrayFrom = Array.from(document.querySelectorAll('div'));
console.log(divArrayFrom);
Keith
  • 22,005
  • 2
  • 27
  • 44
  • 2
    `[].concat` doesn't appear to work if the node list is not concatspreadable? Is that a Babel output? – Bergi Nov 11 '16 at 13:04
  • `Is that a Babel output` Indeed, I just copied the code to babeljs.io, have you an example?, maybe babel does other transformations when required. This of course is only doing testing for this specific case. – Keith Nov 11 '16 at 13:07
  • 3
    The babeljs.io repl has some weird options, it's not really reliable. Using `[].concat` is an incorrect simplification (does only do the same as spread syntax on array arguments), which might be caused by a bug in Babel or some unknown setting. – Bergi Nov 11 '16 at 13:11
  • Oh, right.. I'll have to keep an eye on that one. At first I thought I would delete this post, but thinking on might be worth keeping open as I wasn't aware babel was buggy. What do you think? – Keith Nov 11 '16 at 13:21
  • 1
    Ah, clicking `latest` in babel makes a difference.. I'll update answer.. with new output.. Thanks for the heads up. – Keith Nov 11 '16 at 13:24
  • 2
    Not to mention that you *do not* see "what happens internally" when you look at Babel transpiled code. What a runtime does internally is something else entirely. – Mörre Dec 31 '18 at 09:44
  • @Mörre This post is well over 2 years old now. From what I remember I think Chrome even used babel for a while, until it implemented naive code. But that's not what I really was getting at when I said internally. It might not work for everyone, but when i was learning ESNext features I found babel rather useful. – Keith Dec 31 '18 at 11:26
-3

I need to clarify everyone's answers:

  • The ...foo syntax just spreads (expands) all array values as if they were separate, comma-separated arguments. It does a shallow spread. Any primities (numbers, strings etc) are COPIED and any complex values (objects) are instead REFERENCED.
  • The [] around it is what CREATES a new array.
  • So [...foo] will create a new array and populate it by doing a SHALLOW COPY spreading of all array elements as if they were array constructor arguments which in turn takes all those copied elements and puts them in the new array.
  • Whereas Array.from(foo) will CREATE a new array using the input variable, but is A LOT FASTER because it ONLY creates a SHALLOW COPY (this is FASTER). So it takes the exact input and just puts every variable/reference into the new array.
  • Use Array.from().
Mitch McMabers
  • 3,634
  • 28
  • 27