Spread Syntax ES6

  1. In your example given, there is essentially no difference between the two
  2. .concat is significantly more efficient: http://jsperf.com/spread-into-array-vs-concat because ... (spread) is merely syntax sugar on top of more fundamental underlying syntax that explicitly iterates over indexes to expand the array.
  3. Spread allows sugared syntax on top of more clunky direct array manipulation

To expand on #3 above, your use of spread is a somewhat contrived example (albeit one that will likely appear in the wild frequently). Spread is useful when - for example - the entirety of an arguments list should be passed to .call in the function body.

function myFunc(){
    otherFunc.call( myObj, ...args );
}

versus

function myFunc(){
    otherFunc.call( myObj, args[0], args[1], args[2], args[3], args[4] );
}

This is another arbitrary example, but it's a little clearer why the spread operator will be nice to use in some otherwise verbose and clunky situations.

As @loganfsmyth points out:

Spread also works on arbitrary iterable objects which means it not only works on Arrays but also Map and Set among others.

This is a great point, and adds to the idea that - while not impossible to achieve in ES5 - the functionality introduced in the spread operator is one of the more useful items in the new syntax.


For the actual underlying syntax for the spread operator in this particular context (since ... can also be a "rest" parameter), see the specification. "more fundamental underlying syntax that explicitly iterates over indexes to expand the array" as I wrote above is enough to get the point across, but the actual definition uses GetValue and GetIterator for the variable that follows.


Taking the questions out of order, let's start with the fundamental question: What exactly is the use of spread syntax?

Spread syntax basically unpacks the elements of an iterable such as an array or object. Or, for the more detailed explanation from the MDN Web Docs on spread syntax:

Spread syntax allows an iterable such as an array expression or string to be expanded in places where zero or more arguments (for function calls) or elements (for array literals) are expected, or an object expression to be expanded in places where zero or more key-value pairs (for object literals) are expected.

Following are some simple examples of typical use cases for spread syntax and an example of the difference between spread syntax and rest parameters (they may look the same, but they perform nearly opposite functions).

Function call:

const multiArgs = (one, two) => {
  console.log(one, two);
};

const args = [1, 2];
multiArgs(...args);
// 1 2

Array or string literal:

const arr1 = [2, 3];
const arr2 = [1, ...arr1, 4];
console.log(arr2);
// [1, 2, 3, 4]

const s = 'split';
console.log(...s);
// s p l i t

Object literal:

const obj1 = { 1: 'one' };
const obj2 = { 2: 'two' };
const obj3 = { ...obj1, ...obj2 };
console.log(obj3);
// { 1: 'one', 2: 'two' }

Rest parameter syntax is not the same as spread syntax:

Rest parameter syntax looks the same as spread syntax but actually represents an unknown number of function arguments as an array. So rather than "unpacking" the iterable, rest parameters actually package multiple arguments into an array.

const multiArgs = (...args) => {
  console.log(args);
};

multiArgs('a', 'b', 'c');
// ['a', 'b', 'c']

Spread syntax performance / efficiency:

To address the question about efficiency compared to other methods, the only honest answer is that "it depends". Browsers change all the time and the context and data associated with a particular function create wildly different performance outcomes, so you can find all sorts of conflicting performance timings that suggest spread syntax is both amazingly faster and ridiculously slower than various array or object methods you might use to accomplish similar objectives. In the end, any situation where optimizations for speed are critical should be comparison tested rather than relying on generic timings of simplistic functions that ignore the specifics of your code and data.

Comparison to concat():

And finally a quick comment regarding the difference between spread syntax and concat() shown in the question code. The difference is that spread syntax can be used for a lot more than just concatenating arrays, but concat() works in older browsers like IE. In a situation where you are not concerned about compatibility with older browsers and micro optimizations for speed are unnecessary, then the choice between spread syntax and concat() is just a matter of what you find more readable: arr3 = arr1.concat(arr2) or arr3 = [...arr1, ...arr2].


The output of this example is the same, but it’s not the same behavior under the hood,

Consider ( check the browser's console ) :

var x = [], y = [];

x[1] = "a";
y[1] = "b";

var usingSpread = [...x, ...y];
var usingConcat = x.concat(y);

console.log(usingSpread); // [ undefined, "a", undefined, "b"]
console.log(usingConcat); // [ , "a", , "b"] 

console.log(1 in usingSpread); // true
console.log(1 in usingConcat); // false

Array.prototype.concat will preserve the empty slots in the array while the Spread will replace them with undefined values.

Enter Symbol.iterator and Symbol.isConcatSpreadable :

The Spread Operator uses the @@iterator symbol to iterate through Arrays and Array-like Objects like :

  • Array.prototype
  • TypedArray.prototype
  • String.prototype
  • Map.prototype
  • Set.prototype

(that's why you can use for .. of on them )

We can override the default iterator symbol to see how the spread operator behaves :

var myIterable = ["a", "b", "c"];
var myIterable2 = ["d", "e", "f"];

myIterable[Symbol.iterator] = function*() {
  yield 1;
  yield 2;
  yield 3;
};

console.log(myIterable[0], myIterable[1], myIterable[2]); // a b c
console.log([...myIterable]); // [1,2,3]

var result = [...myIterable, ...myIterable2];
console.log(result); // [1,2,3,"d","e","f"]

var result2 = myIterable.concat(myIterable2);
console.log(result2); // ["a", "b", "c", "d", "e", "f"]

On the other hand, @@isConcatSpreadable is

A Boolean valued property that if true indicates that an object should be flattened to its array elements by Array.prototype.concat.

If set to false, Array.concat will not flatten the array :

const alpha = ['a', 'b', 'c'];
const numeric = [1, 2, 3];

let alphaNumeric = alpha.concat(numeric);

// console.log(alphaNumeric);

numeric[Symbol.isConcatSpreadable] = false;

alphaNumeric = alpha.concat(numeric);

// alphaNumeric = [...alpha, ...numeric];
// the above line will output : ["a","b","c",1,2,3]

console.log(JSON.stringify(alphaNumeric)); // ["a","b","c",[1,2,3]]

However, the spread behaves differently when it comes to Objects since they are not iterable

var obj = {'key1': 'value1'};
var array = [...obj]; // TypeError: obj is not iterable
var objCopy = {...obj}; // copy

It copies own enumerable properties from a provided object onto a new object.

The spread operator is faster, check spread-into-array-vs-concat ( Since Chrome 67 at least )

And check how three dots changed javascript for some use cases, among them is the Destructuring assignment ( Array or Object ) :

const arr = [1, 2, 3, 4, 5, 6, 7];

const [first, , third, ...rest] = arr;

console.log({ first, third, rest });

and splitting a string to an array of characters :

console.log( [...'hello'] ) // [ "h", "e" , "l" , "l", "o" ]