Part 3: What is a Reference in JavaScript?

This is Part III of my I Need To Learn More JavaScript series.

What is a Reference?

I come from a Ruby background and recently started a deep dive into the JavaScript world. I recently was rushing out a feature on a side project of mine, Too Many Miles. I admit it — I didn’t test the new feature and introduced a lovely bug into my UI.

Let’s take a look at the code I wrote…

calculateMaxReward: function (rewards) {
let rewardList = rewards
rewardList.shift();
return _.maxBy(rewards, function (reward) {
return reward.amount;
}).amount;
}

The function above calculateMaxReward returns the highest reward amount in an array after excluding the first reward amount in that array.

I didn’t want to mutate the original rewards array so I first decided (or so I thought) to make a copy of the rewards array in a new variable called rewardList.

let rewardList = rewards

Next I removed the first reward in the ‘newly copied array rewardList’ with rewardList.shift(). Now with the first value removed, I calculated and returned the largest reward amount.

Bugs? But why…

Interestingly the function above calculateMaxReward worked liked I had planned. But now I was getting weird results when I graphed the original rewards array.

Rewards Array Graphed Out

As you can see above the last reward date graphed was for December 22nd, 2016. I knew the original rewards array contained a date from 2017. I even checked my database to confirm this! What was going on here? The January 2017 value was not showing up

The Reason

I modified the original array — rewards.

When I wrote the following code, let rewardList = rewards I thought I was making a copy of the original array rewards. Instead I was making a reference to the original array rewards.

calculateMaxReward: function (rewards) {
let rewardList = rewards //THIS DOES NOT COPY THE ARRAY
rewardList.shift();
return _.maxBy(rewards, function (reward) {
return reward.amount;
}).amount;
}

Strings, Numbers, and Booleans

Let’s take a look at the following example.

let age = 100;
let age2 = age;
console.log(age, age2) //prints out 100 100
age = 200;
console.log(age, age2) //prints out 200 100

Strings, numbers, and booleans are primitive values in JavaScript. Changing the value of a variable never changes the underlying primitive value, it just points the variable to a new primitive.

When I wrote let age = 100 it sets the value 100 in memory and points the variable age to the value in memory (100).

When I wrote let age2 = age it copied the value in memory (100) and set it to a new value in memory (100). It then pointed the variable age2 to this new value in memory.

We now have 2 completely separate values in memory.

Finally when I wrote age = 200 it took the value in memory, 100 and reassigned it to a new value in memory 200.

Thank you Tae Kim for this!

In the figure above, Box 1 and Box 2 under Primitives are values stored in memory. When I wrote let age2 = age I literally copied over the memory value in box 1 and created a new memory value in box 2.

We now have 2 completely separate values in memory. They just happen to have the same primitive value (number 100).

The takeaway is that numbers, strings, and booleans in JavaScript are stored by a value in memory.

However Objects and Arrays are stored by a reference.

…and this is where my bug comes back into play.

Objects and Arrays

Objects and arrays are stored by a reference. What does that mean?

Let’s say you have an array and you want to make a copy of it. So you might write the following code below.

const characters = [“Mickey”, “Minnie”, “Homer Simpson”]

let disneyCharacters = characters

console.log(disneyCharacters)
//prints [“Mickey”, “Minnie”, “Homer Simpson”]

So far so good — this might actually work. Ok now let’s say that you wanted to modify the newly copied array disneyCharacters because we ALL know that Homer Simpson is not a Disney character. So you might try something like this…

disneyCharacters[2] = “Donald Duck”

Now when we console.log the new array disneyCharacters we get the following…

disneyCharacters
//prints [“Mickey”, “Minnie”, “Donald Duck”]

Still working! But what happens when we view the original array characters?

characters
[“Mickey”, “Minnie”, “Donald Duck”]

What just happened?

I thought we made a copy of the array. Why did the original array change?

We created a reference to the original array and NOT a copy of the original array.

We updated disneyCharacters, but disneyCharacters is just a reference to the original array characters.

Take a look at the Non-Primitives table below. When you write let age2 = age you are NOT copying box 1 value into a new memory value. You are making a reference (box 2 arrow to box 1) the original object in box 1.

Let’s look at non-primitives

let disneyCharacters = characters = is a reference to the original array.

Basically this reference is just a pointer that points the the original object which in our case is an array of characters. When we updated the reference (disneyCharacters) we update the original object (array characters).

To solve this we need to make a copy of the array.

How to Copy an Array in JavaScript?

I found 4 ways to do this. There are probably more. Yay JavaScript.

4 WAYS TO COPY A JAVASCRIPT ARRAY
Array.prototype.slice()
let disneyCharacters = characters.slice();
Array.prototype.concat()
let disneyCharacters = [].concat(characters);
Array.from()
let disneyCharacters = Array.from(characters);
ES6 Spread Syntax
let disneyCharacters = […characters];

What is a prototype? Read my article here.

Knowing what we know now, let’s copy the array correctly. Onward!

const characters = [“Mickey”, “Minnie”, “Homer Simpson”]
let disneyCharacters = […characters] //using the ES6 spread syntax
disneyCharacters[2] = “Donald Duck” //updating new array

disneyCharacters
[“Mickey”, “Minnie”, “Donald Duck”] //new updated array
characters
[“Mickey”, “Minnie”, “Homer Simpson”] //doesn't affect original array

Now back to my original bug..

OLD CODE — I made a reference to the array

calculateMaxReward: function (rewards) {
let rewardList = rewards //This is the reference
rewardList.shift();
return _.maxBy(rewards, function (reward) {
return reward.amount;
}).amount;
}

NEW CODE — I make a copy of the array

calculateMaxReward: function (rewards) {
let rewardList = […rewards]; //This is the copy
rewardList.shift();
return _.maxBy(rewardList, function (reward) {
return reward.amount;
}).amount;
}

Bug fixed.

How to Copy an Object?

Like arrays, plain JavaScript objects must also be copied or the will succumb to the same problems with referencing.

Let’s say you have an object and try to copy it incorrectly.

const mouse = {
height: '4ft',
optimismLevel: 'high'
}
const mickeyMouse = mouse;
mickeyMouse.optimismLevel = "ecstatic"
mickeyMouse
//prints Object {height: "4ft", optimismLevel: "ecstatic"}
mouse 
//prints Object {height: "4ft", optimismLevel: "ecstatic"}

The original mouse object was updated. Just like arrays, when we wrote const mickeyMouse = mickey we made a reference to the original object and not a copy of it.

To copy an object we use the Object.assign() method. Look at this example below:

const mickeyMouse = Object.assign({}, mouse , { optimismLevel: ‘ecstatic’ })
mickeyMouse
Object {height: “4ft”, optimismLevel: “ecstatic”}
mouse 
Object {height: “4ft”, optimismLevel: “high”}

Object.assigns()’s first parameter is a new object you will be creating;{}. The second parameter is the object you wish to copy which in our case is the mouse object, and the third optional parameter are the object’s attributes you wish to modify when you copy the object.

For most cases this works wonderfully, but unfortunately Object.assign() does not work in all cases. Object.assign only copies one level deep, and if you have nested object data, it won’t copy it.

Here is an example of nested object data.

const mouse = {
height: ‘4ft’,
optimismLevel: ‘high’,
friends: {
friend1: ‘Goofy’,
friend2: ‘Minnie’
}
}
const mickeyMouse = Object.assign({}, mouse) //copies mouse object

mickeyMouse.friends
//prints Object {friend1: "Goofy", friend2: "Minnie"}
mickeyMouse.friends.friend1 = "Pluto" //updates new object friend1

mickeyMouse.friends
//prints Object {friend1: "Pluto", friend2: "Minnie"}
mouse.friends
// pritns Object {friend1: "Pluto", friend2: "Minnie"}

As you can see when I changed the new object mickeyMouse friends it ALSO changed the original object mouse despite using Object.assign() !

How do you get around this?

Clone Deep

Typically you want to avoid this, but if you need to copy nested data, I would recommend using Lodash’s cloneDeep function which recursively runs their clone function. Yes you could write it yourself, but this will probably be most performant (but still not too performant).

const mouse = {
height: ‘4ft’,
optimismLevel: ‘high’,
friends: {
friend1: ‘Goofy’,
friend2: ‘Minnie’
}
}
const mickeyMouse = _.cloneDeep(mouse)

mickeyMouse.friends
//prints Object {friend1: “Goofy”, friend2: “Minnie”}
mickeyMouse.friends.friend1 = “Pluto”

mickeyMouse.friends
//prints Object {friend1: “Pluto”, friend2: “Minnie”}
mouse.friends
//prints Object {friend1: “Goofy”, friend2: “Minnie”}

As you can see friend1 is now different between the two objects.

Well that is all folks! Hit me up if you have any questions! Keep Chugging People.