Spread Operator

The spread operator '…' was first introduced in ES6. It quickly became one of the most popular features. So much so, that despite the fact that it only worked on Arrays, a proposal was made to extend its functionalities to Objects. This feature was finally introduced in ES9.

The goal of this tutorial, which is divided into two parts, is to show you why the spread operator should be used, how it works, and deep dive into its uses, from the most basic to the most advanced. 

Why you should use the spread operator

Spread Operator : is a operator that pulls out all the element of an array and gives them to you as standalone list of elements


What makes them “iterable”? These kinds of JavaScript types can be traversed in some sequential fashion. For example, you can use a for loop on an array, or with object literals you can use for…in loops.    

"Allow me to introduce you to immutability:"

From Oxford Lexico: Immutability -  unchanging over time or unable to be changed.

In software development, we use the term immutable to refer to values whose state cannot change over time. In fact, most of the values that we normally use (Primitive values, such as Strings , Integers etc.) are immutable. 

However, JavaScript has a peculiar behavior when it comes to Arrays and Objects; they are, in fact, mutable. This can become a big problem. Here's an example, illustrating why:

const mySquirtle = {
name: 'Squirtle',
type: 'Water',
hp: 100
};
const anotherSquirtle = mySquirtle;
anotherSquirtle.hp = 0;
console.log(mySquirtle); //Result: { name: 'Squirtle', type: 'Water', hp: 0 }
view rawobject-mutation.js hosted with ❤ by GitHub

As you can see in the previous code fragment, we have a Squirtle. Our Squirtle has a hp of 100, since we just visited the Pokemon Center. 

Since we want another Squirtle, we declare the variable anotherSquirtle, assigning our original Squirtle as its value. After an arduous battle, anotherSquirtle is defeated. We therefore access anotherSquirtle's hp, and change it to 0. The next step is to check on our original Squirtle. We console.log and…

Wait, what? Our original Squirtle's hp is down to 0! How can this be? What happened to our poor Squirtle? JavaScript mutation happened. Let me explain what's going on. 

When we created the anotherSquirtle variable, and assigned our original Squirtle as its value, what we really did was assign a reference to the memory location of the original Squirtle Object. This is because JavaScript Arrays and Objects are reference data types. Unlike Primitive data types, they point to the memory address where the actual Object/Array is stored.

To make it easier to understand, you can imagine reference data types as pointers to a global variable. By changing a reference data type's value, what we are really doing is changing the value of the global variable. 

This means that, when we changed the anotherSquirtle's hp value to 0, we were really changing the hp value of the Squirtle Object stored in memory to 0. This is why mySquirtle's hp value is 0, because mySquirtle holds a reference to the Object stored in memory, which we changed via the anotherSquirtle variable. Thank you JavaScript.

How do we solve this problem?

To avoid the accidental mutation of variables, what we have to do is create a new instance of our Array/Object whenever we want to copy an Array/Object. How do we achieve this?
 
With the spread operator!! :)

How does the spread operator work?

From the MDN docs: 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
.
To put it simply, the spread operator '…' spreads the items that are contained in an iterable (an iterable is anything that can be looped over, like Strings, Arrays, Sets…) inside a receiver (A receiver is something that receives the spread values). Here are several simple examples with Arrays that will allow you to understand it better:

const numbers = [1, 2, 3];
console.log(...numbers); //Result: 1 2 3
const pokemon = ['Squirtle', 'Bulbasur', 'Charmander'];
console.log(...pokemon); //Squirtle Bulbasur Charmander
const pokedex = [
{ name: 'Squirtle', type: 'Water' },
{ name: 'Bulbasur', type: 'Plant' },
{ name: 'Charmander', type: 'Fire' }
];
console.log(...pokedex); //{ name: 'Squirtle', type: 'Water' } { name: 'Bulbasur', type: 'Plant' } { name: 'Charmander', type: 'Fire' }

As you can see, when we use the spread operator on an Array, we obtain each individual item contained in the Array. In all the previous cases, the receiver was a function, the console.log function. Easy enough, right?

Cloning Arrays and Objects

Now that we now how the spread operator works, we can make use of it to copy Arrays and Objects immutably. How? By spreading the contents, and then using either the Array or Object literals ([] and {} respectively) to generate a new instance of the Array/Object. Let's take the previous Squirtle example, and fix it, by cloning the *mySquirtle * variable immutably:

const mySquirtle = {
name: 'Squirtle',
type: 'Water',
hp: 100
};
const anotherSquirtle = { ...mySquirtle };
anotherSquirtle.hp = 0;
console.log(anotherSquirtle); //Result: { name: 'Squirtle', type: 'Water', hp: 0 }
console.log(mySquirtle); //Result: { name: 'Squirtle', type: 'Water', hp: 100 }
view rawimmutable-object.js hosted with ❤ by GitHub

By destructuring the contents of the mySquirtle variable with the spread operator, and using the Object literal, we are creating a new instance of the Squirtle Object. This way, we prevent accidental variable mutation

To copy an Array, we use exactly the same syntax:

const pokemon = ['Squirtle', 'Bulbasur', 'Charmander'];
const pokedex = [...pokemon];
pokedex.push('Cyndaquil');
console.log(pokemon); //[ 'Squirtle', 'Bulbasur', 'Charmander' ]
console.log(pokedex); //[ 'Squirtle', 'Bulbasur', 'Charmander', 'Cyndaquil' ]
view rawimmutable-array.js hosted with ❤ by GitHub

Note: Bear in mind the fact that the spread operator only performs shallow copies. This means that if you have a reference data type stored inside your Array/Object, when you make a copy with the spread operator, the nested Array/Object will contain a reference to the original, and will thus be mutable.

Converting Array-like Objects to Arrays

Array-like Objects are very similar to Arrays. They usually have numbered elements and a length property. However, they have one crucial difference: Array-like Objects do not have any of the Array functions.

Among the Array-like Objects are the HTML node lists returned by most DOM methods, the arguments variable generated automatically in every JS function and a few others.

With the same syntax as when cloning arrays, we can use the spread operator to transform Array-like structures to Array, as an alternative to using Array.from. Here's an example, converting a NodeList to an Array: 

const nodeList = document.getElementsByClassName("pokemon");
const array = [...nodeList];
console.log(nodeList); //Result: HTMLCollection [ div.pokemon, div.pokemon ]
console.log(array); //Result: Array [ div.pokemon, div.pokemon ]

With this technique, we can transform any Array-like structure to Array, and thus have access to all the Array functions.

The spread operator as an argument

Some functions accept a variable number of parameters. A great example of these types of functions are the ones in the Math collection. For our example, let's pick the Math.max() function, which accepts n numeric parameters, and returns the largest one. Imagine we have an Array of numbers, which we want to pass to the Math.max() function. How do we do it? 

We could do something like this (don't hate me for the following code):

const numbers = [1, 4, 5];
const max = Math.max(numbers[0], numbers[1], numbers[2]);
const max = Math.max(numbers) // NAN (not a number)
console.log(max); //Result: 5

But, of course, doing this would be suicide. What if we had 20 values? Or 1000? Are we really going to access each value by index? The answer is no. As we already know, the spread operator takes an Array and extracts each individual value. This is just what we're looking for! Therefore, we can do this:

const numbers = [1, 4, 5, 6, 9, 2, 3, 4, 5, 6];
const max = Math.max(...numbers);
console.log(max); //Result: 9
view rawmath-max-spread.js hosted with ❤ by GitHub

Spread operator to the rescue!

Adding new elements 

Adding items to an Array

To add new elements to an array, we first spread the Array's contents, and use the Array literal [] to create a new instance of the Array, containing the original array's contents, plus the values we want to add :

const pokemon = ['Squirtle', 'Bulbasur'];
const charmander = 'Charmander';
const cyndaquil = 'Cyndaquil';
const pokedex = [...pokemon, charmander, cyndaquil];
console.log(pokedex); //Result: [ 'Squirtle', 'Bulbasur', 'Charmander', 'Cyndaquil' ]

As you can see, we can add as many new items as we want. 

Adding properties to an Object

By using the same syntax as with Arrays, we can easily add new properties when cloning an Object. To switch it up a little, here's a different syntax to add properties to an Object (it can also be used with Arrays):

const basicSquirtle = { name: 'Squirtle', type: 'Water' };
const fullSquirtle = {
...basicSquirtle,
species: 'Tiny Turtle Pokemon',
evolution: 'Wartortle'
};
console.log(fullSquirtle); //Result: { name: 'Squirtle', type: 'Water', species: 'Tiny Turtle Pokemon', evolution: 'Wartortle' }

As you can see, we can declare and initialize new variables directly inside the Object literal, instead of doing so outside. 

Merging Arrays/Objects

Arrays

We can merge two arrays, by spreading them and using the Array literal, like in the previous examples. However, instead of simply adding a new element, we're going to add another (spread) array:

const pokemon = ['Squirtle', 'Bulbasur', 'Charmander'];
const morePokemon = ['Totodile', 'Chikorita', 'Cyndaquil'];
const pokedex = [...pokemon, ...morePokemon];
console.log(pokedex); //Result: [ 'Squirtle', 'Bulbasur', 'Charmander', 'Totodile', 'Chikorita', 'Cyndaquil' ]
view rawmerge-arrays.js hosted with ❤ by GitHub

It also works if we have an Array of Objects:

const pokemon = [
{ name: 'Squirtle', type: 'Water' },
{ name: 'Bulbasur', type: 'Plant' }];
const morePokemon = [{ name: 'Charmander', type: 'Fire' }];
const pokedex = [...pokemon, ...morePokemon];
console.log(pokedex); //Result: [ { name: 'Squirtle', type: 'Water' }, { name: 'Bulbasur', type: 'Plant' }, { name: 'Charmander', type: 'Fire' } ]

Objects

We can merge two (or more) Objects into a single Object, by using the same syntax as before (you may have noticed by now, that the spread operator is used in a very similar same way, both for Arrays and Objects):

const baseSquirtle = {
name: 'Squirtle',
type: 'Water'
};
const squirtleDetails = {
species: 'Tiny Turtle Pokemon',
evolution: 'Wartortle'
};
const squirtle = { ...baseSquirtle, ...squirtleDetails };
console.log(squirtle); //Result: { name: 'Squirtle', type: 'Water', species: 'Tiny Turtle Pokemon', evolution: 'Wartortle' }
view rawmerge-objects.js hosted with ❤ by GitHub

const p5 = {...p1,hobbies : [...p1.hobbies]}

undefined

p5

{name: "dinesh", age: 13, hobbies: Array(3)}

p1

{name: "dinesh", age: 13, hobbies: Array(3)}

p1.hobbies

(3) ["cooking", "playing", "coding"]

p5

{name: "dinesh", age: 13, hobbies: Array(3)}

p5.hobbies

(3) ["cooking", "playing", "coding"]

p5.hobbies.push('jumping')

4

p5.hobbies

(4) ["cooking", "playing", "coding", "jumping"]

p1

{name: "dinesh", age: 13, hobbies: Array(3)}

p1.hobbies

(3) ["cooking", "playing", "coding"]

"here , we created new array and copy new value array as well. we are able to use spread operator on key-value in objects as well."


Conclusion

In this first part of the tutorial we have learnt why we should use the spread operator (immutability!), how it works, and several basic uses of said operator.

Comments

Popular posts from this blog

JavaScript — Double Equals vs. Triple Equals - weird things on JavaScript

06_Understanding Execution Context and Execution Stack in Javascript

Creating and inserting element- part-2