In this sense, shallow just means that we are copying on the first level. For example, if we have an array of arrays:
const arr1 = [1, 2, 3];
const arr2 = [4, 5, 6];
const arrAll = [arr1, arr2];
What does it mean to copy arrAll? Do we want just a new array but have the values contained in the same? With primitives it is easy because we are copying their values. But if I make a copy of arrAll, does that mean that I want the references to arr1 and arr2 to stay the same or do I want shallow copies of those too?
In the first case, the shallow copy:
const arr1 = [1, 2, 3];
const arr2 = [4, 5, 6];
const arrAll = [arr1, arr2];
const arrAll2 = [...arrAll];
console.log(arrAll === arrAll2);
// false
console.log(arrAll[0] === arrAll2[0]);
// true
console.log(arrAll[1] === arrAll2[1])
// true
arrAll[0] = ['a', 'b', 'c'];
console.log(arrAll[0]);
// ['a', 'b', 'c']
console.log(arrAll2[0]);
// [1, 2, 3]
arrAll[1][0] = 'ribbit';
console.log(arrAll[1]);
// ['ribbit', 5, 6]
console.log(arrAll2[1]);
// ['ribbit', 5, 6]
So, this kind of copying (shallow copy or shallow clone) just does a true copy at the first level, the level of the arrAll, but if there are reference types on the level below it, they will not be “copies” in the sense that we mean. True, the reference (address) will be copied, but as discussed before, it is still pointing to the same place in memory so any changes made to that spot in memory will affect all reference types that are pointing to that spot in memory. For a true, “deep” copy/clone, we would need to go recursively through the data structure and make new copies of all the reference types. Since in this case we know we only have two levels, we could just do this:
const arr1 = [1, 2, 3];
const arr2 = [4, 5, 6];
const arrAll = [arr1, arr2];
const arrAll2 = arrAll.map(arr => [...arr]);
console.log(arrAll === arrAll2);
// false
console.log(arrAll[0] === arrAll2[0]);
// false
console.log(arrAll[1] === arrAll2[1])
// false
arrAll[0] = ['a', 'b', 'c'];
console.log(arrAll[0]);
// ['a', 'b', 'c']
console.log(arrAll2[0]);
// [1, 2, 3]
arrAll[1][0] = 'ribbit';
console.log(arrAll[1]);
// ['ribbit', 5, 6]
console.log(arrAll2[1]);
// [4, 5, 6]
Now we have a true “deep” copy. Notice the last two log statements. As said, this only works if there are two levels, “array or arrays” (or objects or a different reference type). If you don’t know the depth, then you need some kind of recursive solution.
Which is a better copy? Shallow or deep? It depends. Sometimes you definitely want one, sometimes you definitely want the other. It just depends on your needs. But you should be aware of the difference.
Don’t get frustrated if this is a little confusing. Even experienced devs sometimes have to stop and thing about it from time to time.
Another example of a shallow clone would be an address book. Let’s say I have an address book and you want a copy. I would just copy over those addresses. They would still point to the same house. If you looked up my address for Amy Andersen and went to it and painted her house blue, the house to which my address for Amy Andersen is pointing is also now blue - they are the same address, just in two different address books.
A deep copy of an address book would mean building a completely new house with all new stuff that are an exact copy of the old house with all new stuff - but everything is an exact copy. So, if we repainted one house, the other would not be affected. It has a new address but it is listed under Amy Andersen and before any changes are made, standing in those houses, you cannot tell the difference. But any change made to one does not affect the other - they are two different houses with different addresses - they are copies, but they are separate.
Of course, a deep copy doesn’t make sense in the context of an address book, but I hope it makes clearer the distinction.