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" ]