Skip to content

Values and References in JavaScript

Posted on:August 16, 2023 at 03:20 PM

Ever wondered why exactly we say that objects in JavaScript are “mutable”, but strings or numbers are not? Read on to discover a (not really) new way to think about the different types of values in JavaScript.

Table of contents

Open Table of contents

In the beginning there were Values…

In JavaScript, there are two main value types:

  1. Primitive values: number, string, boolean, null, undefined, BigInt and Symbol.
  2. Objects: including object and array literals, functions, Date objects, regular expressions…

Immutable vs Mutable values

A value is mutable when it’s possible to change it. But, what does changing a value mean? Aren’t we changing values whenever we reassign a var or let variable?

To understand how immutability works, we need to reconsider how we think about variables and values.

Variables point to values

Let’s say we have the following code snippet:

let greeting = 'hello';

When someone starts learning JavaScript for the first time, usually they are told that variables are boxes that hold values:

image

However, it’s more accurate to think about variables pointing to values:

image

Primitive values are immutable. When we reassign a variable that points to an immutable value, we are not changing the value itself. If we do this:

let greeting = 'hello';
greeting = 'hi';

We are just changing to what value the variable is pointing to:

image

The old value remains unchanged. It’s not possible to update it (the value itself) in any way. If there aren’t any variables pointing to that value, and therefore it’s not being used, the JavaScript engine will eventually delete it in a process called garbage collection.

This means that the difference between let and const variables is not whether we can change their value, but whether we can reassign them: that is, whether we can change to what value they are pointing to.

Consider now this piece of code:

let greeting = 'hello';
let welcome = greeting;

Initially, both greeting and welcome point to the same value:

image

If we reassign the greeting variable, will the value of welcome also change?

let greeting = 'hello';
let welcome = greeting;
greeting = 'hi';

Not really! After reassigning the greeting variable, it will point to the new value, but the welcome variable still points to the original value:

image

Reference values

So now we’ve seen how primitive values work. What happens with objects, then?

At first, it may seem that they work the same as primitive values. Consider this code snippet:

let person = {
  name: 'Jane',
  lastName: 'Doe',
};

person = {
  name: 'Sally',
  lastName: 'Lockhart',
};

If we reassign the person variable to a different object, the original object will be left alone, and the variable will just point to a new one:

image

But… what happens if we update a property of the object?

To understand it better, let’s change a bit our object representation:

image

All properties are like variables inside the object, but each property also points to a value, just like a regular variable does. So, if we update one of the properties:

let person = {
  name: 'Jane',
  lastName: 'Doe',
};

person.lastName = 'Lockhart';

This is what’s happening under the hood:

image

The object is still the same, but one of its properties has been mutated, and now points to a different value.

What’s the consequence of this behavior if we point two different variables to the same object?

Consider this piece of code:

let person = {
  name: 'Jane',
  lastName: 'Doe',
};

let personCopy = person;

As we’ve learned so far, both person and personCopy would point to the same object:

image

Can you guess what would happen if we updated the lastName property on the personCopy object?

let person = {
  name: 'Jane',
  lastName: 'Doe',
};

let personCopy = person;
personCopy.lastName = 'Lockhart';

console.log(personCopy.lastName); // ??
console.log(person.lastName); // ??

Let’s check out the visual representation of what happens under the hood:

image

First, the lastName property stops pointing at the string value "Doe" to start pointing to the string value "Lockhart". So far, so good. The issue is that objects are not primitive values: a variable that points to an object is actually pointing to a reference of that object. Since personCopy points to the same object reference that person does, it also updates person.lastName! We have mutated the original object without realizing it.

If we wanted to avoid accidentally mutating our object, we would need to create a new object that has all the properties of the original one, for example using the spread operator:

let person = {
  name: 'Jane',
  lastName: 'Doe',
};

let personCopy = { ...person };
personCopy.lastName = 'Lockhart';

console.log(personCopy.lastName); // "Lockhart"
console.log(person.lastName); // "Doe"

Are there other implications of objects being reference values?

Consider now this piece of code:

let person1 = {
  name: 'Jane',
  lastName: 'Doe',
};

let person2 = {
  name: 'Jane',
  lastName: 'Doe',
};

console.log(person1 === person2); // ??

Let’s see what happens line by line. First, we create the person1 object:

image

Then, we create the person2 object:

image

This is the current picture of our code:

image

As we can see, person1 and person2 are pointing to two different object references. With all we’ve learned so far, what is going to get logged into the console?

let person1 = {
  name: 'Jane',
  lastName: 'Doe',
};

let person2 = {
  name: 'Jane',
  lastName: 'Doe',
};

console.log(person1 === person2); // false

person1 and person2 are not equal. Even though they point to objects that look exactly the same, each of them points to a different object reference, therefore they are two different values.

Recap

Let’s review the main ideas we’ve learned in this post:

  1. There are two types of values: primitive values and objects.
  2. Variables are not boxes that hold values, but rather point to them.
  3. Primitive values are immutable: they cannot be changed or destroyed. Every time we reassign a variable, we are not changing or destroying the original value, just pointing the variable to the new value.
  4. Objects are reference values: they point to a reference in memory. The object itself cannot be changed (just like any other value), but the properties inside of it can be mutated.

Bonus

If you feel like going deeper on the differences between primitive and reference values, check out these two blog posts:

That’s it for today! I hope you learned something new, and that you have now a better visual representation of how variables and values work under the hood :D