Why does changing my copy change the original! A JavaScript Dilemma
Tags: javascript, webdev, beginners
Published: 2021-03-08 13:41:31

Have you ever been working on your JavaScript code and changing the value of an object you were *sure* that you made a safe copy of only to have the original object change too?

And then you looked up why the heck that was happening, you read a blog post saying "Use `Object.assign()` instead". But you do that and it *still* freaken happens?

Then someone says that you have to download `lodash` and use the `cloneDeep()` method? And still no one has explained just what the hell is going on?

Yeah, I hate that.

Anyway, here's why it's happening.

> Want a YouTube version of this article? Watch it here:
>
> {% youtube W9ZzxgML6Po %}

## The values of variables

This is probably some fairly common code that you've seen before if you've done any kind of JavaScript coding:

``` javascript
let counter = 2;
let peopleArray = ['John'];
let languages = {
    en: 'English',
    fr: 'French',
    zh: 'Chinese'
};
```

These are all common ways of declaring a variable and defining what its value should be. You have a `counter` set to the number `2`, a `peopleArray` set to an array with one string in it, `'John'`, and `languages` set to an object with keys of `en`, `fr`, and `zh` with the values `'English'`, `'French'`, and `'Chinese'`, respectively.

I also think that I can safely assume that you know that if you call this code:

``` javascript
peopleArray.push('Marty');
console.log(peopleArray);
```

You will see the following output in the console:

``` javascript
[ 'John', 'Marty' ]
```

And a call to:

```javascript
counter++;
console.log(counter);
```

Will give you:

``` javascript
3
```

All of that is what we would expect. Nice, normal, not crazy making behavior from our good friend JS.

Where we run into problems is with this:

``` javascript
let supportedLanguages = languages;
supportedLanguages.de = 'German';

console.log(languages);
```

Which gives us this clearly wrong answer:

``` javascript
{ en: 'English', fr: 'French', zh: 'Chinese', de: 'German' }
```

But I didn't add German to the `languages` object! I added it to the new `supportedLanguages` object! Ah!

Why did this happen? How can we anticipate it and prevent it and, just as important, talk to other developers about it in the future?

Well, let me tell you.

## What exactly is in a JavaScript variable?

When we think about JavaScript variables, what lives in those variables? How you think about this can help us understand the issue that we're seeing.

Most of the time, we probably don't think about this. Or we think that what's on the right side of the `=` is what lives in it. But that's only sort of true.

Here's how I want you to think about JavaScript variables from now on.

> JavaScript variables only hold one thing.

That makes sense on the surface. Of course they only hold one thing.

But arrays and objects hold more than one thing, of course. Surely, I don't mean those?

Oh, but I do! Allow me to explain.

Many of the data types in JavaScript represent one thing. Like `number`s and `boolean`s. Another type can be treated in this same category---because of the way that it's programmed in JavaScript---`string`s. So you can consider that when you put one of these pieces of data in a variable, that's what the variable has in it.

``` javascript
let counter = 1;
let shouldContinue = true;
let name = 'Marty';
```

Here, the variable `counter` contains the value of `1`. If we set a new value, we are replacing that value:

``` javascript
counter = 1;
```

If we are copying the value to another variable, it is indeed copying it and not doing something we don't expect:

```javascript
let extraCounter = counter; // Copies the number 1 from counter to extraCounter
let oldShouldContinue = shouldContinue; // Copies true from shouldContinue to oldShouldContinue
let originalName = name; // Also puts 'Marty' from name to originalName
```

If you keep this mental model[^mental-model] for `number`s, `boolean`s, and `string`s, you'll be just fine. It's probably the one you're expecting anyway.

[^mental-model]: A mental model is a way of thinking about a [programming concept](https://jerickson.net/learning-programming-is-all-about-learning-concepts/), not necessarily the way that it's actually programmed. It helps you picture in your head how things are working so that you can reason about and problem solve with them.

### Object and Array variables are different

While the above works when thinking about `number`s, `boolean`s, and `string`s, it doesn't work when thinking about `object` and `array` variables. That's because `object`s and `array`s hold more than one thing.

And since they contain more than one thing, they can't fit into a variable. So...what's in those variable?

### Leave your number at the tone

Imagine, if you will, your phone's address book. You've got a lot of entries in there and if you scroll through, you'll see all the names of the people that you know in there. If you click on one of those names, does that person spring out of your phone?

Of course not! Phones don't hold people! But they can hold numbers. And that number acts as a link between you and that person. If you call that number, you can then talk to the actual person.

Well, that's how `object`s and `array`s work in JavaScript too! What's stored in the variable? An address to the `object` or `array`!

```javascript
let person = { name: 'Anna', occupation: 'Developer' };
```

So what does `person` contain? You can think of it as the address to the object on the right side, which is also called the **reference** in programming circles.

```javascript
let person = { name: 'Anna', occupation: 'Developer' };
// person contains something like an address that points to the object,
// but doesn't actually contain the object.
```

It's like a phone number for data! When the variable is used, it is calling the object and asking the object to do something:

```javascript
console.log(person.name);
//                ^--- ring, ring, can I have your name?
```

The `.` is often called the **dereference operator** for this very reason. It *dereferences*, or calls, the object.

This address business is something that JavaScript hides behind the scenes and you will never see it, except in a case like this:

``` javascript
let person = { name: 'Anna', occupation: 'Developer' };
let aNewPerson = person; // We copied the address, not the object!
```

In the above example, `person` contains an address and when `aNewPerson` "copies" `person`, it's actually copying the address, not the object! It's like having two people in your contacts that have the same phone number. When you call them, you'll connect with the same person on the other end, no matter what you change the names to.

So this is why, if we change the object `aNewPerson` points to, it'll also change the object `person` is pointing to!

``` javascript
let person = { name: 'Anna', occupation: 'Developer' };
let aNewPerson = person; // We copied the address, not the object!

aNewPerson.name = 'Marie';
console.log(person);
```

Can you guess what this prints?

``` javascript
{ name: 'Marie', occupation: 'Developer' }
```

And the same is true of `array`s:

``` javascript
let names = [ 'John' ];
let copyOfNames = names;
// That only copied the address to the array, it did not copy the array!

copyOfNames.push('Marty');
console.log(names);
```

Will show:

``` javascript
[ 'John', 'Marty' ]
```

It was the same array all along!

## Solutions to copy

### `Object.assign()`

So, now that we know `object`s and `array`s copy `reference`s and not `value`s, how do we avoid the worst of the mistakes when working with them?

The first is to just keep in mind that `=` will copy the address and then any changes will happen to the object that they both point to. Usually, that's not what you want.

So the first thing to look at *for `object`s only* is the `Object.assign()` function. This does what is called a **shallow copy**, meaning any direct member is copied. So if you have a simple, flat object, this should work fine:

``` javascript
let myPhone = {
    manufacturer: 'Motorola',
    speed: 'LTE'
};
let yourPhone = Object.assign({}, myPhone);

yourPhone.manufacturer = 'Apple';
console.log(myPhone);
console.log(yourPhone);
```

This will create a new object---the `{}` above as the first argument to `Object.assign()`---and then copies the values of the second argument---`myPhone` in this case---to that new object. We got this output:

``` javascript
{ manufacturer: 'Motorola', speed: 'LTE' } // myPhone
{ manufacturer: 'Apple', speed: 'LTE' } // yourPhone
```

If you have simple data like this, this will work great. But it doesn't do a **deep copy**. A **deep copy** is where all the values, no matter how deep in the structure, are copied to the new object. In the case above with `Object.assign()`, it only copies the first level and that means that any objects at that level will have their references copied instead!

``` javascript
let goodBook = {
    author: {
        first_name: 'Brené',
        last_name: 'Brown'
    },
    title: 'Daring Greatly'
};

let scaryBook = Object.assign({}, goodBook);
scaryBook.title = 'The Shining';
scaryBook.author.first_name = 'Steven';
scaryBook.author.last_name = 'King';

console.log(goodBook);
```

What does that print? Shock of shocks!

``` javascript
{
  author: { first_name: 'Steven', last_name: 'King' },
  title: 'Daring Greatly'
}
```

Does it make sense yet why that would happen?

If `Object.assign()` is only copying the first level, that means that it copied `goodBook.title` and put the value in the new object. But when it copied `goodBook.author` it took the *reference* to the author object and copied it. So both books are stuck with the same author and changing it in one changes it in the other! This is why you can't always use `Object.assign()`.

So the rule of thumb is:

> If the object you are copying **does not contain an object or array**, **use `Object.assign()`**.

### `slice()`

`slice()` is often recommended to copy arrays. Suffice it to say, it has the same problems with `Object.assign()`.

``` javascript
let books = [
    'The Alchemist',
    'A Tale of Two Cities',
    {
        title: 'Fight Club'
    }
];

let happyBooks = books.slice();
happyBooks[1] = 'The Mystery of the Ghostly Face'; // This won't change books
happyBooks[2].title = 'The Cat in the Hat'; // But this will because of the object

console.log(books);
```

So, again like `Object.assign()`:

> If the array you are copying **doesn't contain an object or array** , **use `slice()`**.

### `_.cloneDeep()`

How do you make sure you actually get a copy? Sadly, the answer lies outside of JavaScript and in other libraries that you will need to import. There is no native function in JavaScript that can do this. You could write your own function to make deep copies, but there are functions already written---and tested---that we can use if we import them into our project.

One of the most popular is `cloneDeep()` from the [lodash library](https://lodash.com/).

> If your object or array is any kind of **complex**, **use `cloneDeep()`** or something similar from a library your are already using in your project.