Inside the JavaScript Memory Box: Visualizing Variables, References, and Copies
There is a difference in the way JavaScript refences variables based on whether they are primitive or non-primitive
Primitive Types: string, number, bigint, Boolean, symbol, null,
undefined
Non-Primitive: Arrays, Objects
Each variable in JS is stored in a memory “box”
Assignments:
But
when another variable tries to assign this value, JS thinks
“wait, is
this a primitive or non-primitive? Because if it is primitive, I will create a
new box for this new variable with the same content. But if it’s not, I will
just create a refence to the existing box”
PRIMITIVE:
Analogy: Let's say you have a note that says 5. You give a copy of this note to your friend. Friend now updates this copy to 10. But your copy still says 5
let a = 5;
let b = a;
b = 10; //a remains unchanged
NON-PRIMITIVE:
Analogy: Let's say you have a note with address "123 Elm Street". In that house lives a cat named "Tom". You give a copy of this note to your friend. Friend now goes to this house and re-names the cat to "Jerry".When you go to this house, you will now find a cat named "Jerry" and not "Tom"
let a = { foo: "bar" };
let b = a;
b.foo = "baz"; //now a is also updated to { foo: "baz" }
But what if b didn't change the property but re-assigned itself a new object?
let a = { foo: "bar" }
let b = a;
b = { foo: "baz" }; //its a complete re-assignment so b has its own memory box. So a is not updated.
Using fancy words: "Primitive values are copied directly. Objects are copied by sharing a reference to the same underlying data."
Shallow Copy and Deep Copy
SHALLOW COPY
Also means using a spread operator.address: {
type: "apartment",
unit: 3806
}
}
Level 1 : Has primitive
Level 2: Has non-primitive
let b = { ...a }
When shallow copy is done using spread operator, level 1 is primitive type, hence b has its own copy (not shared)
But level 2 is non-primitive, it is passed by refence (shared)
Example:
let a = {
name: "tom",
address: {
type: "apartment",
unit: 3806
}
}
let b = { ...a }
b.name = "Jerry"; //ONLY b changes (primitive at level 1)
b.address.type = "office"; // BOTH a and b change (shared object at level 2)
Final values of a and b
|
a |
b |
|
{ name:
"Jerry",
address: {
type: "office" //changed
unit: 3806 } } |
{ name:
"Jerry",
address: {
type: "office"
unit: 3806 } } |
Same with arrays
Example 1:
let a = [ 1,2,3];
let b = [...a]; //---> b gets its own copy, nothing is shared
b[1] = "changed"
console.log(a) // [1, 2, 3] no change
console.log(b) //[1, 'changed', 3]
Example 2:
DEEP COPY
Deep copy (using structuredClone()) creates a completely independent clone of the original object.
Nothing is shared. It recursively copies every nested level, so a and b share no references.
Updating any property inside b does not affect a.
Example:
let a = {
name: "tom",
address: {
type: "home",
building: {
name: "concorde",
unitDetails: {
unitNum: 2029,
unitFloor: 20
},
}
}
};
let b = structuredClone(a);
// Mutations to b (even deeply nested) won't affect a
b.address.type = "office";
b.address.building.name = "Wildstone";
b.name = "jerry";
Final values of a and b
|
a |
b |
|
{ "name": "tom", "address": { "type": "home", "building": { "name": "concorde", "unitDetails": { "unitNum": 2029, "unitFloor": 20 } } } } |
{ "name": "jerry", "address": { "type": "office", "building": { "name": "Wildstone", "unitDetails": { "unitNum": 2029, "unitFloor": 20 } } } } |
Same with Arrays.
They hold their own copies and nothing is shared
Example:
let a = [
{ name: "tom" },
{ name: "jerry" }
];
let b = structuredClone(a);
b[0].name = "changed";
console.log(a[0].name); // "tom" (unchanged)
console.log(b[0].name); // "changed"
|
Type |
Behavior |
What’s
Shared |
Example |
|
Shallow
copy |
Copies
only the first level |
Nested
objects are shared |
{ ...a }, Object.assign() |
|
Deep copy |
Recursively
copies all levels |
Nothing
shared |
structuredClone(a) |
Comments
Post a Comment