I am extremely confused for days now regarding the true definition of a shallow copy and a deep copy.
When I read the mdn docs (https://developer.mozilla.org/en-US/docs/Glossary/Shallow_copy) on shallow copy, it all made sense. The first paragraph clearly explains what a shallow copy is. The docs says
A shallow copy of an object is a copy whose properties share the same references (point to the same underlying values) as those of the source object from which the copy was made. As a result, when you change either the source or the copy, you may also cause the other object to change too — and so, you may end up unintentionally causing changes to the source or copy that you don't expect.
I totally got that part. From my understanding, the below code example is the example of a shallow copy since changing either the source or the copy causes the other object to change too.
let a = {
food: "pasta",
restaurantName: "myPastPlace"
}
let b = a
b.food = "hamburger"
console.log(b.food) //hamburger
console.log(a.food) //hamburger
So, the confusing part was that when I use the spread syntax to make a copy. Is this deep copy or shallow copy? Because for the first level deep, to me, the spread syntax(operator) is making a deep copy. However, the MDN doc says the spread syntax creates a shallow copy rather than a deep copy.
In JavaScript, all standard built-in object-copy operations (spread syntax, Array.prototype.concat(), Array.prototype.slice(), Array.from(), Object.assign(), and Object.create()) create shallow copies rather than deep copies.
let a = {
food: "pasta",
restaurantName: "myPastPlace"
}
let b = {...a}
console.log(b)
b.food = "hamburger"
console.log(b.food) //hamburger
console.log(a.food) //pasta
A variable can contain either a value (in case of primitive values, like 1
), or a reference (in case of objects, like { food: "pasta" }
. Primitive types can only be copied, and since they contain no properties, the shallow/deep distinction does not exist.
If you considered references themselves as primitive values, b = a
is a copy of a reference. But since JavaScript does not give you direct access to references (like C does, where the equivalent concept is a pointer), refering to copying a reference as "copy" is misleading and confusing. In context of JavaScript, "copy" is a copy of a value, and a reference is not considered a value.
For non-primitive values, there are three different scenarios:
b = a
, merely point to the same object; if you modify a
in any way, b
is affected the same way. Intuitively you'd say that whichever object you modify it is reflected in the copy, but it would be wrong: there is no copy, there is just one object. Regardless of which reference you access the object from, it is the same entity. It is like slapping Mr. Pitt, and wondering why Brad is mad at you — Brad and Mr. Pitt are the same person, not clones.let a = {
food: "pasta",
contents: {
flour: 1,
water: 1
}
}
let b = a;
a.taste = "good";
a.contents.flour = 2;
console.log(b);
b.contents.flour
is affected by the change in a
(because b.contents
and a.contents
refer to the same object, { flour: 1, water: 2 }
), but a.taste
does not exist (since a
and b
are objects in their own right).let a = {
food: "pasta",
contents: {
flour: 1,
water: 1
}
}
let b = {...a};
a.taste = "good";
a.contents.flour = 2;
console.log(b);
b.taste
and b.contents.flour
are unaffected by changes in a
.let a = {
food: "pasta",
contents: {
flour: 1,
water: 1
}
}
let b = structuredClone(a);
a.taste = "good";
a.contents.flour = 2;
console.log(b);
Note that at this time structuredClone
is still pretty new; if any users are using older browsers, you might need to use a polyfill.