Moxie and Gus start out with the same number of toys. But then Gus starts stealing from Moxie (he’s cute but a bit of a toy hog). Unfortunately, this code doesn’t keep track of the toy counts as expected. What’s the issue?
Code with error
const moxieToyCounts = {
mice: 4,
feathers: 3,
hairTies: 25,
};
const gusToyCounts = moxieToyCounts;
// gus steals a feather
gusToyCounts.feathers += 1;
moxieToyCounts.feathers -= 1;
// this should be reflected in the toy counts...
// ...but it's not
console.log(gusToyCounts.feathers) // 3
console.log(moxieToyCounts.feathers) // 3
Corrected code
const moxieToyCounts = {
mice: 4,
feathers: 3,
hairTies: 25,
};
const gusToyCounts = {...moxieToyCounts};
// gus steals a feather
gusToyCounts.feathers += 1;
moxieToyCounts.feathers -= 1;
// this is now reflected in the toy counts
console.log(gusToyCounts.feathers) // 4
console.log(moxieToyCounts.feathers) // 2
Explanation
You may have heard of mutability before. Objects and arrays in JavaScript are mutable, which means a couple things things:
- Their properties can be updated even if they’ve been declared as a
const
- They represent a chunk of memory, not an actual value.
Let’s tackle the second item: an object variable represents a memory location instead of an actual value. As an analogy, think of the object variable gusFeathers
as a pointer to a bag of feathers (owned by Gus). If you assign another object variable to equal gusFeathers
(const moxieFeathers = gusFeathers
), this is not going to make a new bag of feathers. Instead, both gusFeathers
and moxieFeathers
lead you to the exact same bag.
So if someone takes a feather from gusFeathers
, then moxieFeathers
loses a feather too. They’re the same thing.
If you want to make a new bag of feathers, there are several ways to go about it. You could use the spread operator , Object.assign()
, or Object.create()
. Any of these make a copy of the object, with its own memory location. So const moxieFeathers = {...gusFeathers}
results in two independent objects. In the “bag of feathers” analogy, someone could set fire to gusFeathers
(I suspect Moxie), and moxieFeathers
will remain intact.
This “pointer to a memory location” concept is also why properties can be updated, even if a mutable object is a const
. When you update a property, you’re not updating the pointer to the location in memory. You’re updating what’s stored at that location. So this is fine:
const moxieToyCount = {};
moxieToyCount.hairTies = 28;
but this will throw an error:
const gusToyCount = {};
const moxieToyCount = { hairTies: 28 };
// Uncaught TypeError: Assignment to constant variable.
gusToyCount = moxieToyCount;
Further Reading
- MDN on shallow copies vs deep copies