Introduction
It’s very common in programming to assign a variable with the value of another variable as in let x = y
. You assume that y
now holds a copy of x
’s value. And for the most part, you'd be right. But then, you run into a bug where changing y
also mysteriously changes x
. Why is that?
The answer lies in one of JavaScript’s most fundamental, and often misunderstood, concepts: assignment by value vs. assignment by reference. While the syntax for assignment is always the same, what happens behind the scenes depends on the type of data you’re working with. Understanding this difference is the key to preventing a whole class of frustrating bugs.
This post is a continuation from my previous post on Understanding Mutability in JavaScript
In this post we’ll cover how assignment is handled in the different data types in JavaScript.
Assigning to a primitive data types
Primitive data types in JavaScript like strings
, numbers
, booleans
, null
, and undefined
are immutable. This means their value cannot be changed after creation. When you assign a primitive from one variable to another, JavaScript creates a new, independent copy of that value.
Think of it like making a photocopy. The original document remains unchanged, and any marks you make on the copy won’t transfer back to the original.
let x = 10; let y = x; console.log(x, y); // 10, 10 y = 20; // Re-assigned to another value console.log(x, y) // 10, 20 ( x remains unchanged )
Drawing from my previous diagram of variables acting as pointers, let’s take a look at what happens inside the computer when we run the code above;
After the first 2 lines of code runs, the value (10) that variable x holds is copied to another location in memory and variable y points to that location and so both variables holds the same value (10) but points to different location in memory.
After y = 20
, the y pointer is moved and points to another location in memory that holds the value 20. This happens because primitive types are immutable and now both x and y points to different locations holding different values.
This same process happens in all primitive data types. When you assign a variable to the value of another variable, both variables remain independent of each other and any operation to one does not affect the other.
Assigning to an object types
Objects (including arrays and functions) are mutable, their internal properties can be changed. When you assign an object from one variable to another, JavaScript does not copy the entire object. Instead, it copies the reference to the object (its memory address). This means both variables are now pointing to the same object in memory. If a property is changed by one of the variables, that change will be reflected in the other.
Imagine you have a shared Google Doc. You send the link to a friend. You and your friend are both accessing the same document. If your friend makes changes, you’ll see them, and vice versa, because you’re both looking at the same, single document. The link is the reference, and the Google Doc is the object in memory.
Consider the example below;
let user = { name: "John", role: "user" }; let admin = user; // A copy of the reference is assigned console.log(user); // { name: "John", role: "user" } console.log(admin); // { name: "John", role: "user" } // Change a property of the object via the 'admin' variable admin.role = "admin"; console.log(admin); // { name: "John", role: "admin" } console.log(user); // { name: "John", role: "admin" } -> The original object was changed
Let's see what happens in the computer memory when we run this code in the diagram below;
After the first two lines of code, both variables points to the same location in memory unlike in primitives where the value is copied to another location in memory. Because objects are mutable, we can directly alter the properties using the dot notation.
So after the line admin.role = “admin”;
the admin variable altered the object in that location in memory and because both variables points to the same address, the change is also reflected in the user variable
This works the same for other object types like array
and function
Breaking the link
A good question now is; “How do we copy the contents of the object to a different address in memory and break the link?” There are a couple of ways to do that, one of which is to simply assign the admin variable to another object that is similar to the first then alter the property we want to change.
let user = { name: "John", role: "user" }; let admin = { name: "John", role: "user" }; // A new object is created at a different memory address console.log(user); // { name: "John", role: "user" } console.log(admin); // { name: "John", role: "user" } admin.role = "admin"; console.log(admin); // { name: "John", role: "user" } console.log(user); // { name: "John", role: "admin" } -> The original object remains unchanged
Although this works, you can tell this method is quite verbose and prone to errors. What if the initial object had over 20 properties? Instead we can use the spread operator (...)
to copy the contents of the first object to another location in memory thereby breaking the link.
let user = { name: "John", role: "user" }; let admin = { ...user }; // A copies the contents to a different memory address console.log(user); // { name: "John", role: "user" } console.log(admin); // { name: "John", role: "user" } admin.role = "admin"; console.log(admin); // { name: "John", role: "user" } console.log(user); // { name: "John", role: "admin" } -> The original object remains unchanged
The image below illustrates what happens in the computer when we run the code above;
When we use the spread operator (...)
, it copies the entire properties of the first object to a different address in memory. You will notice the two variables both points to different locations in memory, now a change to either one will not affect the other.
I would like to point out some extra information about how the spread operator (...)
works:
- It works the same way for arrays as it does for objects.
- It does shallow copying, meaning only the first level of the object is copied to a different address, for deeper nested levels, just the reference is copied.
Comparisons in different data types
JavaScript considers two objects as distinct when they don’t point to the same memory address even if the properties or contents of the objects are exactly the same
You can confirm this by running;
let user = { name: "John", role: "user" }; let admin = { ...user }; console.log(user === admin); // false ( Same properties, different location in memory )
This produces false
because JavaScript compares object types by reference and not the contents of the object. So unless both objects point to the same location in memory, doing comparisons like this will always output false
This is in contrast with primitive types where comparisons is done by value;
Conclusion
In conclusion, a solid understanding of how JavaScript handles variable assignment for different data types is crucial for writing robust and bug-free code. Remember that for primitive types, assignments and comparisons are always done by value, creating a new, independent copy of the data. This means a change to one variable will never affect the original.
However, for object types, the story is different. Assignment and comparison are handled by reference. When you assign one object variable to another, they both point to the same memory location. Any property you alter on one will affect the other, as you are both modifying the same underlying object. To break this shared reference and create a new, distinct object, you can use the spread operator (...)
. It creates a shallow copy, which is a great starting point for making safe modifications without unintended side effects.
While the spread operator is powerful, it has its limitations, particularly with nested objects. In subsequent posts, we’ll delve deeper into more comprehensive methods for copying objects and explore how these same rules of pass-by-value and pass-by-reference apply when passing variables to functions. Knowing the difference between a copy and a shared reference is a fundamental skill that will help you avoid countless headaches in your coding journey.
Thank you for reading!
Top comments (0)