How arrays and objects are really cloned

Oh no! Everything is broken!

You're having a tough day at work. You were tasked as the team developer to create a new image carousel on the company website. There is already an existing carousel on the website, at the top of the page which advertises Bali as a surf destination. Your job is to duplicate the existing carousel code, and create a new one near the bottom of the page which repalces the first Bali picture with an image of the California coastline.

Easy! At least, you thought it would be easy...

When you inspect the code, you see the carousel comes from an external library, that provides you with a simple function to call to create the carousel. All you need to do is provide a data object as an argument to the function.

It works like this:

createImageCarousel( /** DATA OBJECT GOES HERE */ )

The data object the function takes looks like this:

const data = {
    title: 'Surfing in Bali',
    imageWidth: '400px',
    imageHeight: '300px',
    speed: '1.2x',
    transition: 'fadeIn',
    imageSrcs: ['Bali.jpg', 'surf2.jpg', 'surf3.jpg', 'surf4.jpg']
}

createImageCarousel(data)

You think to yourself: okay, I don't want to create a new object literal from scratch. It will be easy for me to just copy the existing object so I get all the properties like speed, imageWidth, imageHeight etc. Then all I need to do is change the title and add the California image to the array in imageSrcs.

Pretty straight forward, right?

You also remember to avoid making the same terrible mistake you made once before - the one that almost got you fired when you were creating new wedding websites from existing ones. You definitely don't want to do this:

const newCarousel = data;
newCarousel.title = 'Summer vacation options'

We know exactly why from our new understanding of the reference values.

Luckily, we know of a quick and easy way to create an exact replica of an object. In fact, there is a new ES6 way with easy syntax, as well as the "old" way of doing it using an object method.

Spread Syntax (ES6)

const newCarousel = { ...data }; 
data === newCarousel // false

Object.assign()

const newCarousel = Object.assign( {}, data ); 
data === newCarousel // false

You decide to use Spread Syntax as it's quick and easy, even though not all browsers have fully implemented it for objects yet.

Fantastic!

All you need to do is call the function with your new object and your work is done:

const newCarousel = { ...data }; 
newCarousel.title = 'Surf in California';
newCarousel.imageSrcs.unshift('California.jpg');

createImageCarousel( newCarousel );

Gotcha: references

Unfortunately your code ended up adding an image of California at the top of the website, not just at the bottom as intended. WHY??!!!

Javascript references have caught us out once more. And to understand the problem, we need to add another layer to our mental model with a bit of story telling.

The trick is to imagine exactly what happens when we try to clone an object. No matter which method we use, spread syntax or object.assign, the fundamentals are the same. By picturing what is happening under the hood, we can identify the reason why we have images of california duplicated across the website.

Let's see an array submarine ship being cloned.

Step 1: A new array is created

The javascript engine constructs a new array ship. This ship is lined up side by side with the original array submarine we are cloning.

Step 2: bridges spread across between the ships

Ever wondered why it's called SPREAD syntax? Well now you know...

Step 3: Genie crew members exchange information

This is the critical moment - the genies from the original array walk out onto the bridge. They are meeting with the genies from the new array that has been created. They have agreed to share the locations of all their value islands.

The only way they can do this is by using the coordinates of the islands, and pointing out the location on a globe.

Memory Trick: If you want to remember the syntax for spread notation, just picture the 3 dots inside as the 3 globes: { ...data }

Identifying the problem

Visualising cloning like this reminds of us of one important thing: genies can't actually see the arrays or objects directly. They can only point out the reference islands on the globe.

What does that mean?

Both the old and new genies for each array share the same reference island!

A cloned object does not mean the object and arrays that are stored as values inside are also cloned. To show this in code:

const newCarousel = { ...data }; 

newCarousel === data // FALSE! Why? Well they are two different objects. 
//We know this because we imagine them lining up side by side

newCarousel.imageSrcs === data.imageSrcs // TRUE! They share the same reference!

By changing the imageSrcs for the newCarousel, we end up changing the imageSrcs for the original data object as well. Even though we cloned the object!!!

I can't tell you how many times this has personally caught me out, and it is an easy mistake to make while creating your programs. In fact, entire libraries have been created to solve this very problem.

The only way we can make sure we aren't unintentionally mutating an object or array is by doing something called a 'deep clone', or a 'deep copy'. That means making sure to check every property value, and if it is an array or object, then cloning that too.

To get the boss off our back, this what we need to do fix our website:

const newCarousel = { ...data }; 
newCarousel.imageSrcs = [ ... data.imageSrcs ]; 

// AND NOW WE CAN MAKE EDITS
newCarousel.title = 'Surf in California';
newCarousel.imageSrcs.shift();
newCarousel.imageSrcs.unshift('California.jpg');

createImageCarousel( newCarousel );

Final thoughts...

Do you see how visualising the code, as silly as it may be, can help us pinpoint where we are going wrong? If we can apply this visual learning technique to cloning, imagine how much it might help with things like promises and constructor functions. All of these concepts have their own nuances, which a visual model can help expose.

Do you agree with me? How can found this material so far? Please let me know in the form below, and look out for the next email from me.


© 2023 Code Imagined - The Great Sync. All Rights ReservedView the Terms & ConditionsView the Privacy Policy
kylo@thegreatsync.com