Reminding Myself that Maps Store Objects by Reference Too
No matter how many times I revisit it, I have pretty consistent track record of being tripped up by how JavaScript assigns values to variables.
Primitives: Assigned by Value
Primitive values (numbers, strings, etc.) are assigned by value, meaning that assigning one variable to another variable assigned to a primitive value will result in two, distinct values being stored in memory. The entire value is copied.
const number1 = 100;
const number2 = number1;
// Variable / Memory Value:
// number1 -> 100
// number2 -> 100
Objects: Assigned by Reference
Everything else (objects, including functions, arrays, etc.) are assigned by reference. Assigning a newly created object to a variable is actually creating a reference to that object’s location in memory. Any further variable assignments will also point to that exact same location.
const object1 = { someProperty: 'some value' };
const object2 = object1;
// Variable / Memory Value:
// object1 -> { someProperty: 'some value' }
// object2 -> object1
And that’s why things like this work. If you mess with the properties of an object — no matter which variable is referencing it — that central value in memory will be changed, impacting every variable pointing to it.
let object1 = { someProperty: 'some value' };
let object2 = object1;
let object3 = object2;
let object4 = object3;
let object5 = object4;
object5.someProperty = 'some OTHER value!';
console.log(object1);
// { someProperty: 'some OTHER value!' }
A Map() Follows the Same Rules
This is a pretty fundamental concept in JavaScript, but that didn’t stop me from forgetting about it while dealing with a Map()
. Just like a regular, old variable, a value inside a Map() is stored differently depending on the value’s type — primitive or otherwise. From MDN:
If the value that is associated to the provided key is an object, then you will get a reference to that object and any change made to that object will effectively modify it inside the Map object.
In my case, I was working with somthing like this (highly contrived):
const aMap = new Map();
const anObj = {
name: 'Alex'
}
aMap.set('a', anObj);
aMap.set('b', anObj);
aMap.set('c', anObj);
aMap.get('c').name = 'Bob';
console.table(Array.from(aMap.entries()));
While I was intending to modify the name
only by on the a
key, the results prints as follows:
| (index) | 0 | 1 |
-----------------------------------
| 0 | 'a' | { name: 'Bob' } |
| 1 | 'b' | { name: 'Bob' } |
| 2 | 'c' | { name: 'Bob' } |
Bob is everywhere. In hindsight, this is no surprise. Primitives by value, objects by reference. If I want these values to be updated independently, the answer is to duplicate the object, reserving it a new, distinct place in memory:
const aMap = new Map();
const anObj = {
name: 'Alex'
}
- aMap.set('a', anObj);
+ aMap.set('a', {...anObj});
- aMap.set('b', anObj);
+ aMap.set('b', {...anObj});
- aMap.set('c', anObj);
+ aMap.set('c', {...anObj});
aMap.get('c').name = 'Bob';
console.table(Array.from(aMap.entries()));
And with that, you’re released to update properties freely:
| (index) | 0 | 1 |
------------------------------------
| 0 | 'a' | { name: 'Alex' } |
| 1 | 'b' | { name: 'Alex' } |
| 2 | 'c' | { name: 'Bob' } |
Just one “Bob,” as desired.
Only a Matter of Time
Stay tuned for another post basically covering the same concept within another context after it inevitably trips me up again.
Alex MacArthur is a software engineer working for Dave Ramsey in
Nashville-ish, TN.
Soli Deo gloria.
Get irregular emails about new posts or projects.
No spam. Unsubscribe whenever.