Cloning an Array in JavaScript: A Cautionary Tale
Tags: javascript, arrays
Published: 2019-12-04 01:16:58

In JavaScript, don’t do this:
```javascript
let a = [1, 2, 3]
let b = a
```
Got it? Okay cool, we’re done here…pack it up guys. 📦

Seriously though, if you’re new to JavaScript, or even not so new, make sure you know the right way to clone an array because if you mess it up, it can lead to some pretty horrible bugs. 

##A Cautionary Tale

There I was, coding away on my final project at Flatiron School. You know, the big one that’s gonna show the world what I’m made of. I didn’t realize it at the time, but I was about to write some code that was gonna land me in a world of hurt. 

Here's a version of what I wrote. I stripped it down to make the mistake easier to spot.
```javascript
const numbers = [ 1, 7, 4 ]

const array = [
    {a: 'value1'},
    {a: 'value2'},
    {a: 'value3'}
]

array.forEach( obj => obj['b'] = numbers)

console.log('array before change = ', array)
//-> array before change =  [
//     { a: 'value1', b: [ 1, 7, 4 ] },
//     { a: 'value2', b: [ 1, 7, 4 ] },
//     { a: 'value3', b: [ 1, 7, 4 ] }
// ]

array[0].b.push(5)

console.log('array after change = ', array)
//-> array after change =  [
//     { a: 'value1', b: [ 1, 7, 4, 5 ] },
//     { a: 'value2', b: [ 1, 7, 4, 5 ] },
//     { a: 'value3', b: [ 1, 7, 4, 5 ] }
// ]
```
<img src=https://media.giphy.com/media/v2OWXglWr9PAk/giphy.gif width=500>
That's right Lego Batman, what the heck? We only added a 5 to one of the arrays, but somehow a 5 got added to all of them.

Now in this example, the error in the code is pretty easy to spot. However, if like me you make this mistake in a much more complicated algorithm, you're gonna be pulling your hair out. So don't do it!

###Take Away
```javascript
let a = [1, 2, 3]
let b = a //Don't clone an array like this
```
- This code does not create a copy of 'a' and assign it to 'b', it creates another reference to the original array and assigns that new reference to 'b'.
- Any change to 'a' or 'b' will cause the same change in the other, because 'a' and 'b' are just references to the same array in memory.

##The Right Way to Copy an Array
```javascript
let a = [1, 2, 3]

//Old ES5 way (aka oldie but a goodie)
let b = a.slice()

//New ES6 way #1
let c = [...a]

//New ES6 way #2
let d = Array.from(a)

//New ES6 way #3
let e = Object.assign([], a)

b[1] = 9
c.push(4)
d.unshift(-1, 0)
e.shift()

console.log('a = ', a)
//-> a =  [ 1, 2, 3 ]

console.log('b = ', b)
//-> b =  [ 1, 0, 3 ]

console.log('c = ', c)
//-> c =  [ 1, 2, 3, 4 ]

console.log('d = ', d)
//-> d =  [ -1, 0, 1, 2, 3 ]

console.log('e = ', e)
//-> e =  [ 2, 3 ]
```
<img src=https://media.giphy.com/media/BbHaJjQbNNR28/giphy.gif width=800>
Well I'm happy, Lego Batman and Robin are giving each other high fives...we're good now, right? Well...not quite.

##Beware of Shallow Copies
What happens if we use proper array cloning methods on deeply nested arrays?
```javascript
let a = [1, [2, 4], [3, 6]]

let b = a.slice()
let c = [...a]
let d = Array.from(a)
let e = Object.assign([], a)

b[0] = 100
b[1][0] = 9


console.log('a = ', a)
console.log('b = ', b)
console.log('c = ', c)
console.log('d = ', d)
console.log('e = ', e)
//-> a =  [ 1, [ 9, 4 ], [ 3, 6 ] ]
//-> b =  [ 100, [ 9, 4 ], [ 3, 6 ] ]
//-> c =  [ 1, [ 9, 4 ], [ 3, 6 ] ]
//-> d =  [ 1, [ 9, 4 ], [ 3, 6 ] ]
//-> e =  [ 1, [ 9, 4 ], [ 3, 6 ] ]
```
<img src=https://media.giphy.com/media/26xBw8wK4kCDjEUzm/giphy.gif width=500>
I'm as surprised as you are, Lego Batman. Reassigning the b[0] to 100 only affected array 'b', but reassigning b[1][0] = 9 changed all the arrays?

If we look into this, we find that even the proper methods for copying arrays in JavaScript are only doing a shallow copy. This means only the first level of the nested array is being copied. The deeper levels are being referenced.

This comes back to the fact that variables store references to arrays and objects, not the array or object itself. So when 'a' is cloned, the above methods are copying references to the nested arrays into a copy of the outermost array.

Primitives (string, number, bigint, boolean, null, undefined, and symbol), on the other hand, are actually copied into the new array.

If you want to deeply clone your arrays, you'll either have to write your own algorithm to do that, or you can use a third party method like the Lodash method _.clonedeep().

##Conclusion
When cloning arrays in Javascript, use one of these methods (these examples assume arry = [1, 2, 3]):
- let clonedArray = arry.slice()
- let clonedArray = [...arry]
- let clonedArray = Array.from(arry)
- let clonedArray = Object.assign([], arry)

Beware of the fact that these methods only do a shallow copy. If you need to deeply clone a nested array, do one of the following:
- Write your own algorithm to deeply clone your nested array
- Use the Lodash method _.clonedeep()

##Postscript
I love listening to lofi hip hop while I code. Something about it really helps me focus and get into a flow. 

With that in mind, I thought it would be fun to start adding a Song of the Week to my blogs. Hopefully, you'll enjoy coding to them as much as I do.

###The Song of the Week

- Shiny Eyes - Oui Lele
- SoundCloud - https://soundcloud.com/ouilele/shiny-eyes

##References
Images and Gifs:
[Cloned Sheep](https://www.wonderopolis.org/wonder/what-is-cloning)
[Lego Batman - What the Heck?](https://giphy.com/gifs/v2OWXglWr9PAk)
[Lego Batman - High Five](https://giphy.com/gifs/lego-batman-the-movie-26xBABFAnPoa4BZMk)
[Lego Batman - Shocked](https://giphy.com/gifs/lego-batman-the-movie-26xBw8wK4kCDjEUzm)

Technical:
[MDN Web Docs - Primitive](https://developer.mozilla.org/en-US/docs/Glossary/primitive)
[MDN Web Docs - Working with Object (scroll down to Comparing Objects)](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Working_with_Objects)
[MDN Web Docs - .slice()](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/slice)
[MDN Web Docs - Spread syntax](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Spread_syntax)
[MDN Web Docs - Array.from()](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/from)
[MDN Web Docs - Object.assign()](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/assign)
[Lodash - _.clonedeep()](https://lodash.com/docs/#cloneDeep)