Reputation: 36703
Consider the following sample code
var x = ["a", "b", "c"];
var z = ["p", "q"];
var d = [...x, ...z];
var e = x.concat(z);
Here, the value of d
and e
are exactly same and is equal to ["a", "b", "c", "p", "q"]
, so,
Upvotes: 35
Views: 10632
Reputation: 223
Context: You want to concatenate two arrays, in order to get a copy "by value" using the three dots spread syntax, but you are working with complex/nested arrays.
Finding: Take care that nested arrays are NOT passed by value, but by reference. In other words, only first level items are passed as a copy "by value". See the example:
sourceArray1 = [ 1, [2, 3] ] // Third element is a nested array
sourceArray2 = [ 4, 5 ]
targetArray = [ ...sourceArray1, ...sourceArray2]
console.log("Target array result:\n", JSON.stringify(targetArray), "\n\n") //it seems a copy, but...
console.log("Let's update the first source value:\n")
sourceArray1[0] = 10
console.log("Updated source array:\n", JSON.stringify(sourceArray1), "\n")
console.log("Target array is NOT updated, It keeps a copy by value: 1\n")
console.log(JSON.stringify(targetArray), "\n\n")
//But if you update a nested value, it has NOT been copied
console.log("Let's update a nested source value:\n")
sourceArray1[1][0] = 20
console.log("Updated source nested array:\n", JSON.stringify(sourceArray1), "\n")
console.log("Target array is updated BY REFERENCE!\n")
console.log(JSON.stringify(targetArray)) // it is not a copy, it is a reference!
console.log("\nCONCLUSION: ... spread syntax make a copy 'by value' for first level elements, but 'by reference' for nested/complex elements (This applies also for objects) so take care!\n")
Upvotes: 0
Reputation: 1256
Spread syntax allows an iterable to be expanded in places where zero or more elements are expected. This high level explanation can be confusing, so a 'real-world' example of this is as follows:
Without the spread syntax you could update objects multiple times like so:
//If I needed to change the speed or damage at any time of a race car
const raceCar = {name: 'Ferrari 250 GT'};
const stats = {speed: 66, damage: 1, lap: 2};
raceCar['speed'] = stats.speed;
raceCar['damage'] = stats.damage;
Alternatively, a cleaner solution is to create a new object with the spread syntax:
//Creates a new object with priority from left to right
const lap1 = { ...raceCar, ...stats }
//Or a specific variable:
const enterPitStop = {...raceCar, speed: 0 }
In essence, rather than mutating the original object of raceCar, you will be creating a new immutable object.
It is also helpful when adding new values to arrays. With spread you can push/unshift multiple variables by copying the former array. Before spread, you would push like so:
var raceCars = ['Ferrari 250 GT', 'Le Mans Series', '24 Heures du Mans'];
//Sometimes, you will need to push multiple items to an array, which gets messy in large projects!
raceCars.push('Car 1');
raceCars.push('Car 2');
raceCars.push('Car 3');
Instead, you would copy the array and add it to a new variable or the same one for simplicity.
//Push values to array
raceCars = [...raceCars, 'Car 1', 'Car 2', 'Car 3'];
//This is dynamic! Add the values anywhere in the array:
//Adds the values at the front as opposed to the end
raceCars = ['Car 1', 'Car 2', 'Car 3', ...raceCars];
//Another dynamic examples of adding not in the front or back:
raceCars = ['Car 1', 'Car 2', ...raceCars, 'Car 3'];
I encourage you to view the more detailed documentation on the Mozilla Developer Website.
Upvotes: 0
Reputation: 15120
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]
.
Upvotes: 8
Reputation: 1
const colours = ['Blue','Red','Black']; // Simple array.
const my_colours = ['Blue','Red','Black','Yellow','Green'];
const favourite_colours = [...my_colours,'grey']; //[...] spread Operator access data in another array.
Upvotes: 0
Reputation: 17654
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 :
(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" ]
Upvotes: 4
Reputation: 83
There is no difference between these two in given example. For concatenation, we can use concat method over spread operator. However, use of spread operator is not limited to the concatenation of arrays.
The spread syntax allows an iterable such as an array expression or string to be expanded. It can be used in following scenarios.
Spread Operator with arrays
Spread Operator with objects
To see how a demonstration of all these uses and to try your hands on code please follow below link (codepen.io)
ES6-Demonstration of Spread Operator
/**
* Example-1: Showing How Spread Operator can be used to concat two or more
arrays.
*/
const americas = ['South America', 'North America'];
const eurasia = ['Europe', 'Asia'];
const world = [...americas, ...eurasia];
/**
* Example-2: How Spread Operator can be used for string to array.
*/
const iLiveIn = 'Asia';
const iLiveIntoArray = [...iLiveIn];
/**
* Example-3: Using Spread Operator to pass arguments to function
*/
const numbers = [1,4,5];
const add = function(n1,n2,n3){
return n1 + n2 + n3;
};
const addition = add(numbers[0],numbers[1],numbers[2]);
const additionUsingSpread = add(...numbers);
/**
* Example-4: Spread Operator, can be used to concat the array
*/
const personalDetails = {
name: 'Ravi',
age: '28',
sex: 'male'
};
const professionalDetails = {
occupation: 'Software Engineer',
workExperience: '4 years'
};
const completeDetails = {...personalDetails, ...professionalDetails};
Upvotes: 1
Reputation: 10508
.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.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.
Spread also works on arbitrary iterable objects which means it not only works on
Array
s but alsoMap
andSet
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.
Upvotes: 30