DEV Community

loading...
Cover image for Understanding the JavaScript Spread Operator - From Beginner to Expert Part 2

Understanding the JavaScript Spread Operator - From Beginner to Expert Part 2

Nya
Full stack dev~
Updated on ・7 min read

Introduction

The spread operator, …, was first introduced in ES6. It quickly became one of the most popular features. So much so that despite the fact 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 to deep dive into its uses, from the most basic to the most advanced. If you haven't read the first part of this tutorial, I encourage you to do so! Here is the link:

Understanding the JavaScript Spread Operator - From Beginner to Expert

Here is a short summary of the contents of this tutorial:

Part 1

  1. Why the spread operator should be used
  2. Cloning arrays/objects
  3. Converting array-like structures to array
  4. The spread operator as an argument
  5. Adding elements to arrays/objects
  6. Merging arrays/objects

Part 2

  1. Destructuring nested elements
  2. Adding conditional properties
  3. Short circuiting
  4. The rest parameter (…)
  5. Default destructuring values
  6. Default properties

Cloning arrays/objects with nested elements

In the first part of this article, we learnt about reference data types, accidental variable mutation, and how we could solve this problem by cloning arrays/objects immutably, with the spread operator. 

However, there's a slight problem with this approach, when it comes to nested reference data types: The spread operator only performs a shallow clone. What does this mean? If we attempt to clone an object that contains an array, for example, the array inside the cloned object will contain a reference to the memory address where the original array is stored… This means that, while our object is immutable, the array inside it isn't. Here's an example to illustrate this:

As you can see, our squirtleClone has been cloned immutably. When we change the name property of the original pokemon object to 'Charmander', our squirtleClone isn't affected, its name property isn't mutated

However, when we add a new ability to the abilities property of the original pokemon object… Our squirtleClone's abilities are affected by the change. Because the abilities property is a reference data type, it isn't cloned immutably. Welcome to the reality of JavaScript :)

One of the solutions to this problem would be to use the spread operator to clone the nested properties, as shown in the following example:

For obvious reasons, this isn't an ideal approach to solving our problem. We would need to use the spread operator for every single reference type property, which is why this approach is only valid for small objects. So, which is the optimal solution? Deep cloning

Since there is plenty to say about deep cloning, I won't be going into too much detail. I'd just like to say that the correct of deep cloning is either using an external library (for example, Lodash), or writing ourselves a function that does it. 


Adding conditional properties

Sometimes we need to add properties to an object, but we don't know whether or not those properties exist. This doesn't pose much of a problem, we can always check if the property exists with an if statement:

There is, however, a much simpler way of achieving the same result, by using short circuiting conditionals with the && operator. A brief explanation:

Short circuiting

When we evaluate an expression with &&, if the first operand is false, JavaScript will short-circuit and ignore the second operand

Let's take a look at the following code:

If starterPokemon.length > 0 is false (the array is empty), the statement will short circuit, and our choosePokemon function will never be executed. This is why the previous code is equivalent to using the traditional if statement.

Going back to our original problem, we can take advantage of the logical AND operator to add conditional properties to an object. Here's how:

What's going on here? Allow me to explain:

As we already know, by using the && operator, the second part of the statement will only be executed if the first operand is true. Therefore, only if the abilities variable is true (if the variable exists), will the second half of the statement be executed. What does this second half do? It creates an object containing the abilities variable, which is then destructured with the spread operator placed in front of the statement, thus adding the existent abilities variable into our fullPokemon object immutably


Before we can introduce our final advanced spreading use, adding default properties to objects, we must first dive into two new concepts: default destructuring values, and the rest parameter. Once we are familiar with these techniques, we will be able to combine them to add default properties to objects.

Default destructuring values

If we try to destructure an array element or object property that doesn't exist, we will get an undefined variable. How can we avoid undefined values? By using defaults. How does this work?

We can assign default values to the variables we destructure, inside the actual destructuring statement. Here's an example:

As you can see, by adding the default value 'Water' to the type variable in the destructuring statement, we avoid an undefined variable in the case of the pokemon object not having the type property.

The rest parameter (…)

You may be surprised to hear that the spread operator is overloaded. This means that it has more than one function. It's second function is to act as the rest parameter.

From the MDN docs: A function's last parameter can be prefixed with ... which will cause all remaining (user supplied) arguments to be placed within a "standard" javascript array. Only the last parameter can be a "rest parameter".

Simply put, the rest operator takes all remaining elements (this is the reason it's named rest, as in the rest of the elements :p ) and places them into an array. Here's an example:

As you can see, we can pass as many abilities as we want to the printPokemon function. Every single value we introduce after the type parameter (the rest of the parameters) will be collected into an array, which we then turn into a string with the join function, and print out.

Note: Remember that the rest parameter must be the last parameter, or an error will occur.

The rest parameter can also be used when destructuring, which is the part that interests us. It allows us to obtain the remaining properties in an object, and store them in an array. Here's an example of the rest parameter used in a destructuring assignment:

As shown above, we can use the rest operator to destructure the remaining properties in the pokemon object. Like in the previous example, our pokemon object can have as many properties as we want defined after the id property, they will all be collected by the rest parameter.

Now that we know how the rest parameter works, and how to apply it in destructuring assignments, let's return to dealing with default properties.


Adding default properties

Sometimes, we have a large amount of similar objects, that aren't quite exactly the same. Some of them lack properties that the other objects do have. However, we need all of our objects to have the same properties, simply for the sake of order and coherence. How can we achieve this? 

By setting default properties. These are properties with a default value that will be added to our object, if it doesn't already have that property. By using the rest parameter combined with default values and the spread operator, we can add default properties to an object. It may sound a bit daunting, but it's actually quite simple. Here's an example of how to do it:

What's going on in the previous code fragment? Let's break it down:

As you can see, when we destructure the abilities property, we are adding a default value ([]). As we already know, the default value will only be assigned to the abilities variable if it doesn't exist in the pokemon object. In the same line, we are gathering the remaining properties (name and type) of the pokemon object into a variable named rest, by making use of the awesome rest parameter

On line 7, we are spreading the rest variable (which, as you can see, is an object containing the name and type properties) inside an object literal, to generate a new object. We are also adding the abilities variable, which in this case, is an empty array, since that is what we specified as its default value on the previous line.

In the case of our original pokemon object already having an abilities property, the previous code would not have modified it, and it would maintain its original value.

So, this is how we add default properties to an object. Let's place the previous code into a function, and apply it to a large collection of objects:

As you can see, all the pokemon in the array now have an abilities property. In the case of charmander and bulbasur, they have an empty array, since that is the default value we assigned. However, the squirtle object maintains its original array of abilities

There are, of course, other ways of adding default properties to an object, mainly by using if statements. However, I wanted to show an interesting new way of doing it, by using a combination of default values, the rest parameter, and the spread operator. You can then choose which approach suits you best :)


Conclusion

This is the second and final part of the Understanding the JavaScript Spread Operator - From Beginner to Expert tutorial. Here's a link to the first part.

In this second part of the tutorial we have learnt some more advanced uses of the spread operator, which include destructuring nested elements, adding conditional properties and adding default properties. We have also learnt three interesting JS concepts: short-circuiting, default destructuring values and the rest parameter. 

I sincerely hope you've found this piece useful, thank you for reading :) If you can think of any more uses of the spread operator or would like to comment something, don't hesitate to reach out, here's a link to my Twitter page.

Discussion (10)

Collapse
alais29 profile image
Alfonsina Lizardo

Thanks a lot for these tutorials! I recently learned about the spread and rest operators on freecodecamp but I didn't fully understand them, your tutoriales helped me a lot! :)

Collapse
nyagarcia profile image
Nya Author

I'm so glad you found them helpful, it's the reason I write them for! :D

Collapse
carlillo profile image
Carlos Caballero

Great!

Collapse
nyagarcia profile image
Nya Author

Thanks! :D

Collapse
equiman profile image
Camilo Martinez

Oh Nya this series was incredible. Thanks a lot.

You have a little mistake, nested-object-clone.js was used two times.

I highly recommended change this gist to dev's liquid tags.

Collapse
miguelmota profile image
Miguel Mota

Nice tutorial!

Collapse
nyagarcia profile image
Nya Author

Thank you :)

Collapse
silvija_pro profile image
Silvija Prozinger

Both of your articles about spread operator really cleared it up for me, thank you for writing this!

Collapse
nyagarcia profile image
Nya Author

Thanks to you for reading!

Collapse
abbiranjan profile image
Alok Ranjan

Thanks again for such a nice tutorial.