I was helping a friend of mine with some technical interview questions, and a lot of them were very much like “what does this code output”-style questions (which I deeply dislike, I think these questions are more indicative of someone knowing really specific aspects of a language and less about software engineering, but I digress). One of them though I thought was interesting enough to write a technical explanation.
Values and objects
Look at this JavaScript code:
let [CLOSED, OPEN] = [{}, {}]
CLOSED = {
cake: OPEN
}
OPEN = {
fish: 5
}
console.log(CLOSED); // cake is {}
Why is it that CLOSED
is not { cake: { fish: 5 }}
?
It’s because of how JavaScript assigns values and objects. In JavaScript, when you assign an object to another variable, you’re not creating a copy of that object. You’re creating a reference to that object.
So what’s happening here is that CLOSED
and OPEN
are both initialized as empty objects {}
. When we assign CLOSED
to { cake: OPEN }
, it’s pointing at that empty OPEN
object.
When you change OPEN
to { fish: 5 }
in this way, it assigns OPEN
to a new object, and leaves the previously empty object behind. I say it like that because CLOSED
still refers to the old empty object, and not the new one!
I say “in this way” because there’s another way to change OPEN
: modify the existing empty object in place , so that CLOSED.cake
references it even as it changes.
let [CLOSED, OPEN] = [{}, {}]
CLOSED = {
cake: OPEN
}
OPEN.fish = 5;
console.log(CLOSED); // This now logs { cake: { fish: 5 }}
Do you see how this is different? Instead of creating an entirely new object for OPEN
to be reassigned to, we modify the properties of OPEN
instead. CLOSED
is still pointing to that object, and it’s just not a “left behind” object anymore!
I hope you don’t have to have this as an interview question, but you may run into similar problems as you build systems that use this as a concept.
User management example
Let’s say you have some kind of user management system. You might have an initial state of user accounts:
let state = {
users: [
{ id: 1, name: "Cassidy", role: "admin" },
{ id: 2, name: "Yesenia", role: "user" },
{ id: 3, name: "Alan", role: "user" },
{ id: 4, name: "Gabor", role: "user" },
]
};
If you wanted to promote a user to be an admin, you’d want to change the user object (by reference):
function promoteToAdmin(user) {
user.role = "admin";
}
(to reiterate: this changes the existing user object)
If you wanted to remove a user, you would want to change the state object (by value):
function removeUserById(userId) {
state.users = state.users.filter(user => user.id !== userId);
}
(this creates a new array with the filtered result)
So, if you want to promote Yesenia to admin:
let yesenia = state.users[1]; // Reference to Yesenia's object
promoteToAdmin(yesenia); // Directly changes her object
console.log(state.users);
Try this in your console! You’ll see that the users
array is all the same, except that Yesenia is an admin now.
If we wanted to remove Alan next:
removeUserById(3);
console.log(state.users);
Again, this does not change the original users
array, but returns a new array and assigns users
to it.
If you run this yourself, note in your console that Yesenia is still an admin, showing that the update persisted!
Wow, I love JavaScript
Good for you. I have a love/hate relationship with it. But I mostly love it too, I guess. Kind of. I should value it more. Eh?? Ehhh????
Okay anyway, I hope this is helpful!
Top comments (2)
This was informative Cassidy, thank you!
This by ref by val is true of literally every programming language. C forces it to be more explicit but this isn't really JS specific advice and is just general this is how programming works.