Javascript object references & creating a copy

Javascript object references & creating a copy

The way that javascript objects store values as variables can cause issues if you are unaware of how it is done. The difference is that values are stored as a reference, in contrast to primitive data types which are stored as the actual value.

As a reminder, the primitive data types include strings, numbers, boolean, null, undefined, bigint, and symbol.

The issue will become more apparent with some examples. Let's create a few variables.

let firstString = 'hello'
let secondString = 'world'
firstString = secondString

Creating two variables with the string values of 'hello' and 'world'. We then set the value of 'firstString' to the value of 'secondString'.

When we console.log the value of those variables, we get the following:

let firstString = 'hello'
let secondString = 'world'
firstString = secondString

console.log('firstString value: ', firstString)
// firstString value: world
console.log('secondString value: ', secondString)
// secondString value: world

This is the result that we expected. We set the firstString variable, then the secondString variable and we then changed the firstString value to be the secondString value. Therefore when we log out the values of each variable, those values are equal to each other.

To prove this, let's try a couple more examples and check their types and equality:

let thirdString = 'goodbye'
let fourthString = 'goodbye'

console.log(typeof(thirdString))
console.log(typeof(fourthString))
console.log(thirdString === fourthString)

// string
// string
// true

The result here is expected. We set the value of each variable to a string, with that value being the same word. Therefore they both show up as string types and return true when tested for equality.

Now that we've tested a few examples with a string, let's do something similar with an object.

const firstObject = {
  firstName: 'Gary',
  skills: ['javascript', 'python', 'HTML', 'CSS']
}

const secondObject = {
  firstName: 'Joe',
  skills: ['NextJs', 'React', 'Typescript']
}

console.log('firstObject value: ', firstObject)
console.log('secondObject value: ', secondObject)

// firstObject value:  {firstName: 'Gary', skills: Array(4)}
// secondObject value:  {firstName: 'Joe', skills: Array(3)}

What can we do if we want to keep the original object, but also have a copy of that object that has an array of skills that have changed?

Let's try and create a copy of the first object and change the array.

const thirdObject = firstObject
thirdObject.skills = ['Rust', 'Clojure']

console.log('thirdObject value: ', thirdObject)
// thirdObject value:  {firstName: 'Gary', skills: Array(2)}

The result looks like we have been successful. Let's check everything and log out our objects again:

console.log('firstObject value: ', firstObject)
console.log('thirdObject value: ', thirdObject)

// firstObject value:  {firstName: 'Gary', skills: Array(2)}
// thirdObject value:  {firstName: 'Gary', skills: Array(2)}

That is a little unexpected, I expected my firstObject to have an array of skills with a length of 4, but it is showing an array of 2 items. This is the same as the thirdObject. Why is that happening? I thought I had only changed the thirdObject arrary?

Let's try another experiment and test the equality of two objects.

const testOne = {
  key: 100
}

const testTwo = {
  key: 100
}

console.log(testOne === testTwo)
// false

That is also an unexpected result given what we saw when we compared equality between firstString and secondString.

My questions:

  1. Why are the strings equal, but the objects are not?
  2. Why did both of my objects change when I thought I only edited one of them?

These outcomes are an effect of how javascript stores the object value as a variable. The primitive data types store the actual value as the variable, but object values are saved as an address to memory and it is a reference to that address that is stored.

This means that when you try and copy the object, as we tried here:

const thirdObject = firstObject

It is not actually creating a copy, it is setting the 'thirdObject' variable as the reference to the address. What this means is that when you are updating the thirdObject variable, you are also updating firstObject, because they are pointing to the same place in memory.

This is also why a variable that is set to an object that looks exactly like another object does not pass the equality test. The variables, while they look the same, are actually referencing different locations within the memory.

So how do we make a copy of a javascript object that allows us to edit our new variable, without changing the original copy?

There are several ways to accomplish this, but the most common way I copy an object is using the spread operator. Here is how we would accomplish what we tried previously:

const firstObject = {
  firstName: 'Gary',
  skills: ['javascript', 'python', 'HTML', 'CSS']
}

const thirdObject = {
  ...firstObject
}

console.log(firstObject)
// {firstName: 'Gary', skills: Array(4)}

console.log(thirdObject)
// {firstName: 'Gary', skills: Array(4)}

We have successfully created a copy of firstObject. Now we can edit thirdObject and make sure that we did not actually change firstObject too.

thirdObject.skills = ['Data Analysis', 'R']

console.log(firstObject)
// {firstName: 'Gary', skills: Array(4)}

console.log(thirdObject)
// {firstName: 'Gary', skills: Array(2)}

Review

In this article, we looked at how javascript stores values in a variable for primitive data types versus objects. We reviewed a few ways that these differences can create issues that are challenging to debug.

We also learned a way that we can copy an object, allowing us to keep our original object while providing a way to edit the copy.

Additional Resources