What is the difference between spread operator
and array.concat()
let parts = ['four', 'five']; let numbers = ['one', 'two', 'three']; console.log([...numbers, ...parts]);
Array.concat()
function
let parts = ['four', 'five']; let numbers = ['one', 'two', 'three']; console.log(numbers.concat(parts));
Both results are same. So, what kind of scenarios we want to use them? And which one is best for performance?
You can use either the spread operator [... array1, ... array2] , or a functional way []. concat(array1, array2) to merge 2 or more arrays.
concat performs at 0.40 ops/sec, while . push performs at 378 ops/sec. push is 945x faster than concat ! This difference might not be linear, but it is already is already significant at this small scale.
The spread operator unpacks elements of iterable objects such as arrays, sets, and maps into a list. The rest paramter is also denoted by three dots (…). However, it packs the remaining arguments of a function into an array. The spread operator can be used to clone an iterable object or merge iterable objects into one.
One of the alternatives to the spread operator is the Object. assign function. Here is the same function using the object. assign function.
concat
and spreads are very different when the argument is not an array.
When the argument is not an array, concat
adds it as a whole, while ...
tries to iterate it and fails if it can't. Consider:
a = [1, 2, 3] x = 'hello'; console.log(a.concat(x)); // [ 1, 2, 3, 'hello' ] console.log([...a, ...x]); // [ 1, 2, 3, 'h', 'e', 'l', 'l', 'o' ]
Here, concat
treats the string atomically, while ...
uses its default iterator, char-by-char.
Another example:
x = 99; console.log(a.concat(x)); // [1, 2, 3, 99] console.log([...a, ...x]); // TypeError: x is not iterable
Again, for concat
the number is an atom, ...
tries to iterate it and fails.
Finally:
function* gen() { yield *'abc' } console.log(a.concat(gen())); // [ 1, 2, 3, Object [Generator] {} ] console.log([...a, ...gen()]); // [ 1, 2, 3, 'a', 'b', 'c' ]
concat
makes no attempt to iterate the generator and appends it as a whole, while ...
nicely fetches all values from it.
To sum it up, when your arguments are possibly non-arrays, the choice between concat
and ...
depends on whether you want them to be iterated.
The above describes the default behaviour of concat
, however, ES6 provides a way to override it with Symbol.isConcatSpreadable
. By default, this symbol is true
for arrays, and false
for everything else. Setting it to true
tells concat
to iterate the argument, just like ...
does:
str = 'hello' console.log([1,2,3].concat(str)) // [1,2,3, 'hello'] str = new String('hello'); str[Symbol.isConcatSpreadable] = true; console.log([1,2,3].concat(str)) // [ 1, 2, 3, 'h', 'e', 'l', 'l', 'o' ]
Performance-wise concat
is faster, probably because it can benefit from array-specific optimizations, while ...
has to conform to the common iteration protocol. Timings:
let big = (new Array(1e5)).fill(99); let i, x; console.time('concat-big'); for(i = 0; i < 1e2; i++) x = [].concat(big) console.timeEnd('concat-big'); console.time('spread-big'); for(i = 0; i < 1e2; i++) x = [...big] console.timeEnd('spread-big'); let a = (new Array(1e3)).fill(99); let b = (new Array(1e3)).fill(99); let c = (new Array(1e3)).fill(99); let d = (new Array(1e3)).fill(99); console.time('concat-many'); for(i = 0; i < 1e2; i++) x = [1,2,3].concat(a, b, c, d) console.timeEnd('concat-many'); console.time('spread-many'); for(i = 0; i < 1e2; i++) x = [1,2,3, ...a, ...b, ...c, ...d] console.timeEnd('spread-many');
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With