DEV Community

Cover image for Deep vs Shallow Copy - with Examples

Deep vs Shallow Copy - with Examples

Laurie on July 24, 2019

I wrote a post a couple of weeks back about the spread operator. 5 Uses for the Spread Operator Lauri...
Collapse
 
juliang profile image
Julian Garamendy • Edited

Hi! Thank you for writing this!
I came across some unexpected results the other day. It may be worth sharing:

const arr = [1,2,3];
arr[10] = 11; // (why would anyone do this is beyond me)

arr.forEach((n,i) => console.log(i,n))
// 0 1
// 1 2
// 2 3
// 10 11

arr.slice().forEach((n,i) => console.log(i,n))
// 0 1
// 1 2
// 2 3
// 10 11

[...arr].forEach((n,i) => console.log(i,n))
// 0 1
// 1 2
// 2 3
// 3 undefined
// 4 undefined
// 5 undefined
// 6 undefined
// 7 undefined
// 8 undefined
// 9 undefined
// 10 11
Collapse
 
laurieontech profile image
Laurie • Edited

Oh interesting. I just played around with this a bit. And found this.

> let arr = [1,2,3]
> arr[10] = 11
11
> arr
[ 1, 2, 3, <7 empty items>, 11 ]

So it makes sense that the spread syntax would copy that same array.

According to the spec, forEach elides missing array items, so all of those undefined elements won't be accounted for. Why it doesn't in the final case I don't know. Will have to come back and look into a bit.

Thanks for the example!

Collapse
 
juliang profile image
Julian Garamendy

I just didn't know that arr.slice() and [...arr] are not equivalent.

Here's another example, without forEach.

arr.slice() vs [...arr]

const arr = [{name:'A'},{name:'B'},{name:'C'}]
arr[10] = {name:'D'}

console.log(arr.slice().map(obj => obj.name)) // ["A", "B", "C", empty × 7, "D"]
console.log([...arr].map(obj => obj.name)) // Uncaught TypeError: Cannot read property 'name' of undefined
Thread Thread
 
laurieontech profile image
Laurie

So this is what I see

let arr = [1,2,3]
> arr[5] = 1
> arr.slice()
[ 1, 2, 3, <2 empty items>, 1 ]
> [...arr]
[ 1, 2, 3, undefined, undefined, 1 ]

But no docs are telling me why that's the case. Still searching because I genuinely want to know!

Thread Thread
 
laurieontech profile image
Laurie • Edited

So the difference is holes in an array versus undefined elements. And that happens due to this:

Also explained this way:

Thread Thread
 
juliang profile image
Julian Garamendy

Awesome! Thank you!

Collapse
 
cecilelebleu profile image
Cécile Lebleu

This is a great write up! Thank you for sharing. I do have a question though.
I don’t understand the example about nested objects; why is copy in the end still the same as the original obj? I feel that with the accompanying explanation maybe the snippet is wrong, but I don’t know much about how these details work; would you mind explaining this one a bit further?

let obj = {a:1, b:2, c: {a:1}}
let copy = {...obj}
obj['c']['a'] = 5
// obj is {a:1, b:2, c: 5}
// copy is {a:1, b:2, c: {a:1}}
Collapse
 
laurieontech profile image
Laurie

I wrote out a full explanation and the realized the snippet is indeed wrong! Thanks for catching that.

Collapse
 
cecilelebleu profile image
Cécile Lebleu

Thanks for clearing it out!

Collapse
 
stereobooster profile image
stereobooster

cursed code

function structuralClone(obj) {
  const oldState = history.state;
  history.replaceState(obj, document.title);
  const copy = history.state;
  history.replaceState(oldState, document.title);
  return copy;
}

source dassur.ma/things/deep-copy/

Collapse
 
dinsmoredesign profile image
Derek D

Indeed. Stringify causes all kinds off problems with Dates.

Collapse
 
savagepixie profile image
SavagePixie

Very interesting and helpful explanation. Thanks!

Collapse
 
laurieontech profile image
Laurie

So glad it was helpful!

Collapse
 
bagwaa profile image
Richard Bagshaw

Great read, this stuff is really handy to know when testing with something like Jest as well ..

Collapse
 
laurieontech profile image
Laurie

You're correct! But for the examples I put in the post it's a good option. Nice to have these additional links as well.