Difference between using a spread syntax (...) and push.apply, when dealing with arrays
Asked Answered
D

7

38

I have two arrays,

const pets = ["dog", "cat", "hamster"]

const wishlist = ["bird", "snake"]

I want to append wishlist to pets, which can be done using two methods,

Method 1:

pets.push.apply(pets,wishlist)

Which results in: [ 'dog', 'cat', 'hamster', 'bird', 'snake' ]

Method 2:

pets.push(...wishlist)

Which also results in: [ 'dog', 'cat', 'hamster', 'bird', 'snake' ]

Is there is a difference between these two methods in terms of performance when I deal with larger data?

Delectate answered 27/9, 2016 at 4:54 Comment(5)
define "larger" data - firefox looks slower with Method 2 ... other browsers may be faster ... try your own benchmarkingBiz
If you're transpiling into ES5, there's a strong possibility that Babel/TypeScript etc. will generate code exactly like pets.push.apply. In any case, the chance that there is any performance difference that would affect the speed of your app is miniscule. Why did you wonder?Gait
In JS you can send indefinite number of arguments to a function but not that indefinite. Depending on the current session available stack size there is a limit like 150-300K arguments max. As per your question push.apply seemed to work faster when i benchmarked.Gynecologist
If you don't need to retain a reference to the array .concat() would be more appropriate.Filler
You are asking for an implementation detail that is, a response varies depending on the browser and on time (because implementation details may change). Conceptually, both techniques are identical.Bifarious
B
34

Both Function.prototype.apply and the spread syntax may cause a stack overflow when applied to large arrays:

let xs = new Array(500000),
 ys = [], zs;

xs.fill("foo");

try {
  ys.push.apply(ys, xs);
} catch (e) {
  console.log("apply:", e.message)
}

try {
  ys.push(...xs);
} catch (e) {
  console.log("spread:", e.message)
}

zs = ys.concat(xs);
console.log("concat:", zs.length)

Use Array.prototype.concat instead. Besides avoiding stack overflows concat has the advantage that it also avoids mutations. Mutations are considered harmful, because they can lead to subtle side effects.

But that isn't a dogma. If you are wihtin a function scope and perform mutations to improve performance and relieve garbage collection you can perform mutations, as long as they aren't visible in the parent scope.

Bifarious answered 27/9, 2016 at 8:10 Comment(0)
S
5

For appending to a large array the spread operator is vastly faster. I don't know how @ftor / @Liau Jian Jie drew their conclusions, possibly bad tests.

test results Chrome 71.0.3578.80 (Official Build) (64-bit), FF 63.0.3 (64-bit), & Edge 42.17134.1.0

It makes sense since concat() makes a copy of the array and doesn't even attempt to use the same memory.

The thing about "mutations" doesn't seem to be based on anything; if you're overwriting your old array, concat() has no benefits.

The only reason to not use ... would be stack overflow, I agree with the other answers that you can't use ... or apply.
But even then just using a for {push()} is more or less twice as fast as concat() in all browsers and wont overflow.
There's no reason to use concat() unless you need to keep the old array.

Stonewall answered 9/12, 2018 at 5:55 Comment(0)
F
5

With push you are appending to the existing array, with spread operator you are creating a copy.

a=[1,2,3]
b=a
a=[...a, 4]
alert(b);

=> 1, 2, 3

 a=[1,2,3]
b=a
a.push(4)
alert(b);

=> 1, 2, 3, 4

push.apply as well:

a=[1,2,3]
c=[4]
b=a
Array.prototype.push.apply(a,c)
alert(b);

=> 1, 2, 3, 4

concat is a copy

a=[1,2,3]
c=[4]
b=a
a=a.concat(c)
alert(b);

=> 1, 2, 3

By reference is preferable, especially for larger arrays.

Spread operator is a fast way of doing a copy which traditionally would be done with something like:

a=[1,2,3]
b=[]
a.forEach(i=>b.push(i))
a.push(4)
alert(b);

=> 1, 2, 3

If you need a copy, use the spread operator, it's fast for this. Or use concat as pointed out by @ftor. If not, use push. Keep in mind, however, there are some contexts where you can't mutate. Additionally, with any of these functions you will get a shallow copy, not a deep copy. For deep copy you will need lodash. Read more here : https://slemgrim.com/mutate-or-not-to-mutate/

Fennessy answered 2/2, 2019 at 0:55 Comment(0)
G
2

Apart from what ftor pointed out, Array.prototype.concat is, on average, at least 1.4x faster than the array spread operator.

See results here: https://jsperf.com/es6-add-element-to-create-new-array-concat-vs-spread-op

You can run the test on your own browser and machine here: https://www.measurethat.net/Benchmarks/Show/579/1/arrayprototypeconcat-vs-spread-operator

Glantz answered 24/10, 2017 at 5:44 Comment(0)
S
2

Interpreting the question as which is more performant in general, using .push() as an example it looks like apply is [only slightly] faster (except for MS Edge, see below).

Here's a performance test on just the overhead on calling a function dynamically for the two methods.

function test() { console.log(arguments[arguments.length - 1]); }
var using = (new Array(200)).fill(null).map((e, i) => (i));

test(...using);

test.apply(null, using)

I tested in Chrome 71.0.3578.80 (Official Build) (64-bit), FF 63.0.3 (64-bit), & Edge 42.17134.1.0, and these were my resulsts after running them a few times on their own The initial results were always skewed one way or the other

benchmark results for the three browsers

As you can see Edge seems to have a better implementation for apply than it does for ... (but don't try to compare the results across browsers, we cant tell whether Edge has a better apply than the others, a worse ..., or a bit of both from this data).
Given this, unless you're targeting Edge specifically, I'd say go with ... just as it reads cleaner, especially if you need to pass an object back in to apply for this.

It's possible that it's dependent on the size of the array, too, so like @Jaromanda X said, do your own testing and change 200 if you need to really make sure.


The other answers interpreted the question as which would be better for .push() specifically, and get caught up on the 'problem' being solved, simply recommending to just use .concat(), which is basically the standard why are you doing it that way? which can irk some people coming from google that aren't looking for solutions to do with .push() (say, Math.max, or your own custom function).

Stonewall answered 8/12, 2018 at 3:21 Comment(0)
Z
2

The answer of user6445533 was accepted as the answer, but I feel the test case is abit weird. That doesn't seems like how you should use spread operator usually.

Why cant just do like :

let newPets = [...pets, ...wishlist]

It will not face any stackoverflow issue as described. Like Hashbrown has mentioned, it may gives you performance benefit as well.

*I'm also in the middle of learning ES6. Sorry if I'm wrong.

Zootechnics answered 5/4, 2020 at 13:29 Comment(0)
E
-2

if you are using ES2015 then the spread operator is the way to go. Using the spread operator your code looks less verbose and much cleaner compared to other approach. When it comes to speed, I believe there will be little to choose between both the approaches.

Enrage answered 27/9, 2016 at 5:22 Comment(2)
Please provide a real test to prove your speed claim. In my tests the spread operation was exceedingly slower than Function.prototype.apply. Such as gist.github.com/joliss/4de65fa03d547fc1c814Bron
Regardless of speed, sometimes you do not want to mutate an array. In those cases spread is always best, when dealing with modern javascript or typescript. For instance, when working in a redux reducer. I'm specifically talking about when comparing to _array.push(). concat is always usable and performant but does not look as good or as deliberate.Patterman

© 2022 - 2024 — McMap. All rights reserved.