JSpread Operator

What is Spread Operator?

The JavaScript ES6 version has brought a whole array of new tools and utilities. One such new feature is the spread operator.

The operator’s shape is three consecutive dots and is written as: ...

It allows an iterable to expand in places where 0+ arguments are expected.

you can use the spread operator on Arrays and objects. you can use them in different cases: Expanding Arrays or objects, Combining or Merging Arrays and Objects, Cloning Arrays and Objects and Using with Math Functions.

Definitions are tough without context. Let’s explore some different use cases:

Expanding Arrays or objects:

Expand arrays or objects with new elements or in other names adding elements or properties to arrays or objects and operations on arrays(push-unshift):

Arrays:

Example1:

let pokemon= ['arbok','kfc','mack'];
//push
pokemon = [...pokemon,'sfdsdfs', 'tyutyu', 'bnmbn' ];
console.log(pokemon) //output: ['arbok','kfc','mack', 'sfdsdfs', 'tyutyu', 'bnmbn']
//unshift
pokemon = ['sfdsdfs', 'tyutyu', 'bnmbn', ...pokemon ];
console.log(pokemon) //output: ['sfdsdfs', 'tyutyu', 'bnmbn', 'arbok','kfc','mack']

Example2:

let arr1 = ['arbok','kfc','mack'];
let arr2 = ['sfdsdfs', 'tyutyu', 'bnmbn' ];
/*
push elements in arr2 to arr1 so the output of arr1 is ["arbok", "kfc", "mack", "sfdsdfs", "tyutyu", "bnmbn"]
*/
arr1 = […arr1, …arr2];
console.log({arr1, arr2});
/*
unishift elements in arr2 to arr1 so output of arr1 is ["sfdsdfs", "tyutyu", "bnmbn", "arbok", "kfc", "mack"]
*/
arr1 = […arr2, …arr1];

Objects:

let pickau = {name: 'pickau'};
let stats = {hp:40, attack: 60, defense: 45};
pickau = {…pickau, …stats};
console.log(pickau)
//Output: {name: "pickau", hp: 40, attack: 60, defense: 45}
//If I did the following:
pickau = {…stats,…pickau};
console.log(pickau);
//Output: {hp: 40, attack: 60, defense: 45, name: "pickau" }

Another Example:

pickau = {…pickau, hp: 45} ;
console.log(pickau)
//Output:{ name: “pickau” , hp: 40 }

Combining or Merging Arrays and Objects:

Let’s say we have lists from two different sources and we want to combine both these sources and make a single list:

let arr1 = ['John', 'Sofia', 'Bob'];
let arr2 = ['Julia', 'Sean', 'Anthony'];
let singleList = […arr1, … arr2];
console.log(singleList); //Output: [ 'Julia', 'Sean', 'Anthony', 'John', 'Sofia', 'Bob' ]
/*====================== With Objects: ======================*/
const obj1 = {name: 'john'};
const stats = {hp:40, attack: 60, defense: 45};
const obj2 = {…obj1, …stats };
console.log(obj2) //output: {name: "john", hp: 40, attack: 60, defense: 45};

Cloning Arrays and Objects:

Clone array:

const arr = [‘1’, ‘2’, ‘3’];
const cloneArr = […arr];
console.log(cloneArr);

Never do the following code with cloning:

const arr = [‘1’, ‘2’, ‘3’];
const cloneArr = arr;
console.log(cloneArr);

why?

Because arrays in JS are reference values, so when you try to copy it using the = it will only copy the reference to the original array and not the value of the array, so if you will do the following code:

const arr = ['1', '2'];
const arr2 = arr;
arr2.push('3');
console.log(arr2); // [ '1', '2', '3' ]  Ahhh , our original arr have changed?!
console.log(arr); // [ '1', '2', '3' ] That's why we need to clone an array:
const arr = ['1', '2'];
const arr2 = [..arr]; // Let's change our arr2 array
arr2.push('3');
console.log(arr2);// [ '1', '2', '3' ]  
console.log(arr); // [ '1', '2' ]

Cloning with Objects:

let obj = {name: 'john'};
let stats = {hp:40, attack: 60, defense: 45};
let obj2 = {...obj};
console.log(obj2) //Output: {name: "john"}
obj2 = {...stats};
console.log(obj2); //Output: {hp: 40, attack: 60, defense: 45};

Using with Math Functions

JavaScript has a Math object which contains several methods to operate with a set of data, i.e. a list of data.

Let us say we want to get the maximum value from the first three numbers of a list:

let arr = [10, 23, 83, -1, 92, -33, 76, 29, 76, 100, 644, -633];
Math.max(arr[0], arr[1], arr[2]);

What if we want to get the maximum of all numbers in a list? What if the list has n number of items? Surely we won't want arr[0], arr[1]... arr[1000].

The spread operator provides a cleaner solution:

let arr = [10, 23, 83, -1, 92, -33, 76, 29, 76, 100, 644, -633];
Math.max(...arr);

The spread operator combines well with Math.min() to find the smallest number in an array:

let arr = [10, 23, 83, -1, 92, -33, 76, 29, 76, 100, 644, -633];
Math.min(...arr);

Accept any number of arguments in a function:

If you don’t know how many arguments you need to accept in an arrow function, you can use the spread operator with the ‘rest’ parameter to make this work:

const sum = (...args) => { 
  let sum = 0;
  for(let i = 0; i < args.length; i++){
    sum += args[i];
  } 
  return sum;
};

console.log(sum(2,3)); //5
console.log(sum(2,3,4)); //9
console.log(sum(2,3,4, 5)); //14

Some Tricks with Spread:

1- First One

let greet = ['Hello', 'World'];
console.log(greet); // Without spread operator
console.log(…greet); // Using spread operator
/* If we run this code we’ll see the following:[‘Hello’, ‘World’]Hello World */

2-Secon Trick:

Sometimes, we may feel the need to convert a String into a list of characters. We can use spread operator for this use-case:

let greetings = “hello”;
let chars = […greetings];
console.log(chars);
/*If we run this code, we’ll be greeted with:[ ‘h’, ‘e’, ‘l’, ‘l’, ‘o’ ] */

3- Organize Properties

Sometimes properties aren’t in the order we need them to be. Using a couple of tricks we can push properties to the top of the list or move them to the bottom.

To move id to the first position, add id: undefined to the new Object before spreading object.

const user1 = { password: 'Password!', name: 'Naboo', id: 300 }
const organize = object => ({ id: undefined, ...object })
console.log(organize(user1)) //Output: //=> { id: 300, password: 'Password!', name: 'Naboo' }
/*To move password to the last property, first destructe password out of object. Then set password after spreading object. */
const user2 = { password: 'Password!', name: 'Naboo', id: 300 }
const organize = ({ password, ...object }) => ({ ...object, password })
console.log(organize(user2))
//Output: //=> { name: 'Naboo', id: 300, password: 'Password!' }

4-Forth Trick:

let arr1 = [‘arbok’,’kfc’,’mack’];
let arr2 = [‘sfdsdfs’, ‘tyutyu’, ‘bnmbn’ ]
arr1 = [arr1, …arr2];//output: [Array(3), “sfdsdfs”, “tyutyu”, “bnmbn”]
arr1 = […arr1, arr2];//output: [“arbok”, “kfc”, “mack”, Array(3)]

5-Fifth Trick:

let obj1 = {name: ‘John’, email: ‘john@example.com’ };
let obj2 = {name: ‘Michael’, email: ‘Michael@example.com’}
obj1 = {obj1, …obj2};
console.log(obj1)
//Output: {obj1: {…}, name: “Michael”, email: “Michael@example.com”}
obj1 = { …obj2, obj1 };
console.log(obj1)
//output: {name: "Michael", email: "Michael@example.com", obj1: {…}}

Shallow Copy & Deep Copy:

Please note spread only goes one level deep when copying an array. So if you're trying to copy a multi-dimensional array, you will have to use other alternatives.

const arr = [ [1, 2], [10] ];
const cloneArr = [...arr];
// Let's change the first item in the first nested item in our cloned array.
cloneArr [0][0] = '👻';
console.log(cloneArr); // [ [ '👻', 2 ], [ 10 ], [ 300 ] ]
// NOOooo, the original is also affected
console.log(arr);
// [ [ '👻', 2 ], [ 10 ], [ 300 ] ]

Here’s an interesting thing I learned. A shallow copy means the first level is copied, deeper levels are referenced.

The problem

The spread syntax and the Object.assign() the method can only make shallow copies of objects. This means that the deeply nested values inside the copied object are put there just as a reference to the source object.

Wrong solutions

Online you will find many wrong suggestions:

  1. Using Object.create():

const copied = Object.create(original)

Here the original object is being used as the prototype of copied.

It looks fine, but under the hoods, it’s not:

const original = {
  name: 'Fiesta'
}
const copied = Object.create(original)
copied.name //Fiestaoriginal.hasOwnProperty('name') //true
copied.hasOwnProperty('name') //false

2. JSON serialization:

This only works if you do not have any inner objects and functions, but just values.

const cloned = JSON.parse(JSON.stringify(original))

But you will have the following side effects:

You will lose any JavaScript property that has no equivalent type in JSON, like Function or Infinity. Any property that’s assigned to undefined will be ignored by JSON.stringify, causing them to be missed on the cloned object.

Also, some objects are converted to strings, like Date objects for example (also, not taking into account the timezone and defaulting to UTC).

So How we can clone an Object with Right Way?

1-Using Lodash Clone And Clonedeep

Lodash comes with two different functions that allow you to do shallow copies and deep copies. These are clone and clonedeep.

Lodash has this nice feature: you can import single functions separately in your project to reduce a lot the size of the dependency.

Take Look at the following example to understand:

const clone = require('lodash/clone'); 
const cloneDeep = require('lodash/clonedeep');

const externalObject = {
  animal: 'Cow'
};

const originalObject = {
  a: 1,
  b: 'string',
  c: false,
  d: externalObject
};

const shallowClonedObject = clone(originalObject);

externalObject.animal = 'Lion';
// The `animal` property in both the originalObject and shallowClonedObject so it is shallow copy
console.log(originalObject);
console.log(shallowClonedObject);


const deepClonedObj = clonedeep(originalObject);

externalObject.animal = 'jiraffi';
// The 'animal' property only in the originalObject changes, because it is deep clone, 
console.log(originalObject);
console.log(deepClonedObject);

2-Using immutability-helper Library:

Mutate a copy of an object without changing the original source.

Setup via NPM

npm install immutability-helper --save

We can get this library via NPM: npm install immutability-helper --save. To deep copy our object, we could use the update() method available in immutability-helper, passing the object we want to copy as the first argument and the actual data to change as the second one:

import update from 'immutability-helper';

const originalObject = {
  a: 1,
  b: 'string',
  c: false,
  d: {
    animal: 'Cow'
  }
};

const deepClonedObj  = update(originalObject, {d: {animal: 'Lion'} } );

// log the copied object to the console
console.log(deepClonedObj); // This will log animal: 'Lion', as expected

// log the source object to the console
console.log(originalObject); // This will correctly log animal: 'Cow', the original Object!

Last updated

Was this helpful?