DEV Community πŸ‘©β€πŸ’»πŸ‘¨β€πŸ’»

DEV Community πŸ‘©β€πŸ’»πŸ‘¨β€πŸ’» is a community of 963,274 amazing developers

We're a place where coders share, stay up-to-date and grow their careers.

Create account Log in
Cover image for Cloning an Array in JavaScript: A Cautionary Tale
Joseph Trettevik
Joseph Trettevik

Posted on • Updated on

Cloning an Array in JavaScript: A Cautionary Tale

In JavaScript, don’t do this:

let a = [1, 2, 3]
let b = a
Enter fullscreen mode Exit fullscreen mode

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.

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 ] }
// ]
Enter fullscreen mode Exit fullscreen mode


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

let a = [1, 2, 3]
let b = a //Don't clone an array like this
Enter fullscreen mode Exit fullscreen mode
  • 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

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 ]
Enter fullscreen mode Exit fullscreen mode


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?

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 ] ]
Enter fullscreen mode Exit fullscreen mode


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

References

Images and Gifs:
Cloned Sheep
Lego Batman - What the Heck?
Lego Batman - High Five
Lego Batman - Shocked

Technical:
MDN Web Docs - Primitive
MDN Web Docs - Working with Object (scroll down to Comparing Objects)
MDN Web Docs - .slice()
MDN Web Docs - Spread syntax
MDN Web Docs - Array.from()
MDN Web Docs - Object.assign()
Lodash - _.clonedeep()

Top comments (0)

🀯

"I made 10x faster JSON.stringify() functions, even type safe"

☝️ Must read for JS devs