DEV Community

Samantha Ming
Samantha Ming

Posted on • Edited on

How to Deep Clone an Array in JavaScript

Code Tidbit by SamanthaMing.com

There are 2 types of array cloning: shallow & deep. Shallow copies only cover the 1st level of the array and the rest are referenced. If you want a true copy of nested arrays, you’ll need a deep clone. For deep clones, go with the JSON way OR better yet use Lodash 👍



const numbers = [1, [2], [3, [4]], 5];

// Using JavaScript
JSON.parse(JSON.stringify(numbers));

// Using Lodash
_.cloneDeep(numbers);


Enter fullscreen mode Exit fullscreen mode

Arrays are Reference Types

In order to understand why there are two types of cloning. Let's dig into the fundamentals and explains what are reference types.

Unlike your primitive types (ie. number or string), arrays are reference types. Which means when you assign an array to a variable, you're assigning a memory address and not the actual array itself. WTH 😱. I know this is a bit confusing. So let's explain with an example.

Copying a Value type

So no biggie here. We're creating a copy of value. And if we change the valueCopy, it doesn't affect the original value. Makes sense - when we change the copy it shouldn't affect the original at all. All good here 👍



let value = 3;
let valueCopy = value; // create copy

console.log(valueCopy); // 3

// Change valueCopy
valueCopy = 100
console.log(valueCopy); // 100

// ✅ Original NOT affected 
console.log(value); // 3


Enter fullscreen mode Exit fullscreen mode

Copying a Reference type

Okay, things are about to get weird now. Let's copy our array using the same method as we did to copy a value type.



let array = [1,2,3];
let arrayCopy = array; // create copy

console.log(arrayCopy); // [1,2,3];

// Change 1st element of the array
arrayCopy[0] = '👻';
console.log(arrayCopy); // [ '👻', 2, 3 ]

// ❌Original got affected
console.log(array); // [ '👻', 2, 3 ]


Enter fullscreen mode Exit fullscreen mode

Why did the original array also got affected? That's because what you copied over is not the array itself but the pointer to the memory space the array occupies. Reference types don't hold values, they are a pointer to the value in memory.

Solution to Copying Reference Types

So the solution is to copy over the value NOT the pointer. Like this:



let array = [1,2,3];
let arrayCopy = [...array]; // create TRUE copy

console.log(arrayCopy); // [1,2,3];

// Change 1st element of the array
arrayCopy[0] = '👻';
console.log(arrayCopy); // [ '👻', 2, 3 ]

// ✅ Original NOT affected 
console.log(array); // [ 1, 2, 3 ]


Enter fullscreen mode Exit fullscreen mode

Shallow vs Deep Clone

When I used spread ... to copy an array, I'm only creating a shallow copy. If the array is nested or multi-dimensional, it won't work. Let's take a look:



let nestedArray = [1, [2], 3];
let arrayCopy = [...nestedArray]; 

// Make some changes
arrayCopy[0] = '👻'; // change shallow element
arrayCopy[1][0] = '💩'; // change nested element
console.log(arrayCopy); // [ '👻', [ '💩' ], 3 ]

// ❌ Nested array got affected
console.log(nestedArray); // [ 1, [ '💩' ], 3 ]


Enter fullscreen mode Exit fullscreen mode

As you can see, the shallow or first layer is fine. However, once we change the nested element, the original array also got affected. So the solution is to do a deep clone:



let nestedArray = [1, [2], 3];
let arrayCopy = JSON.parse(JSON.stringify(nestedArray)); 

// Make some changes
arrayCopy[0] = '👻'; // change shallow element
arrayCopy[1][0] = '💩'; // change nested element
console.log(arrayCopy); // [ '👻', [ '💩' ], 3 ]

// ✅ Nested array NOT affected
console.log(nestedArray); //  1, [ 2 ], 3 ]


Enter fullscreen mode Exit fullscreen mode

Community Input

Values Not Compatible with JSON

@tailcall: One has to be really careful with JSON solution! It doesn't work with values not compatible with JSON. Consider using a library function if you have to work with such data.



function nestedCopy(array) {
    return JSON.parse(JSON.stringify(array));
}

// undefineds are converted to nulls
nestedCopy([1, undefined, 2]) // -> [1, null, 2]

// DOM nodes are converted to empty objects
nestedCopy([document.body, document.querySelector('p')]) // -> [{}, {}]

// JS dates are converted to strings
nestedCopy([new Date()]) // -> ["2019-03-04T10:09:00.419Z"]


Enter fullscreen mode Exit fullscreen mode

deepClone vs JSON

@alfredosalzillo: I'd like you to note that there are some difference between deepClone and JSON.stringify/parse.

  • JSON.stringify/parse only work with Number and String and Object literal without function or Symbol properties.
  • deepClone work with all types, function and Symbol are copied by reference.

Here's an example:



const lodashClonedeep = require("lodash.clonedeep");

const arrOfFunction = [() => 2, {
    test: () => 3,
}, Symbol('4')];

// deepClone copy by refence function and Symbol
console.log(lodashClonedeep(arrOfFunction));
// JSON replace function with null and function in object with undefined
console.log(JSON.parse(JSON.stringify(arrOfFunction)));

// function and symbol are copied by reference in deepClone
console.log(lodashClonedeep(arrOfFunction)[0] === lodashClonedeep(arrOfFunction)[0]);
console.log(lodashClonedeep(arrOfFunction)[2] === lodashClonedeep(arrOfFunction)[2]);


Enter fullscreen mode Exit fullscreen mode

Using Recursion

Tareq Al-Zubaidi: There is another simple and more performant solution to this problem. I would use recursion to solve this.



const clone = (items) => items.map(item => Array.isArray(item) ? clone(item) : item);


Enter fullscreen mode Exit fullscreen mode

See comparison test here


Resources


Thanks for reading ❤
Say Hello! Instagram | Twitter | Facebook | Medium | Blog

Latest comments (31)

Collapse
 
samath24 profile image
Samath24

Just found your ( interesting ) post looking up on how to deep copy an array the right way. While you probably know it by now, the structuredClone() global function now exists since 2022 and is very well supported. It is a good solution to our common headaches !

May this help someone in need, like it did.

Collapse
 
giorgos profile image
GeorgeNeilyo

Amazing post. Simple explanation,simple examples.

Thank you!

Collapse
 
rishisetpal profile image
Rishi Setpal • Edited

There is no point in compairing: new Object with new Object
lodashClonedeep(arrOfFunction)[0] === lodashClonedeep(arrOfFunction)[0]

Instead Compare: old Object with new Object
arrOfFunction[0] === lodashClonedeep(arrOfFunction)[0]


// Just a code snippet
// Change Direactory: cd "current_working_directory"
// npm install lodash or npm install lodash.clonedeep
const lodashClonedeep = require("lodash.clonedeep");
const arrOfFunction = [
    () => 2, 
    { test: () => 3,}, 
    Symbol('4')
];
// deepClone copy by refence function and Symbol
console.log(lodashClonedeep(arrOfFunction));
// JSON replace function with null and function in object with undefined
console.log(JSON.parse(JSON.stringify(arrOfFunction)));
// function and symbol are copied by reference in deepClone
console.log(arrOfFunction[0] === lodashClonedeep(arrOfFunction)[0]);
console.log(arrOfFunction[2] === lodashClonedeep(arrOfFunction)[2]);
Enter fullscreen mode Exit fullscreen mode
Collapse
 
pradyumnagupta profile image
PradyumnaGupta • Edited

structuredClone API is the more cleaner way to do this now and it is accepted by almost all the browsers now :

array2 = structuredClone(array1);

Collapse
 
aloksdiptim profile image
alokz diptim! • Edited

This right here deep copies nested array too

    let cloneMatrix  = [];

    for(let m = 0; m < matrix.length; m++){
        cloneMatrix.push([...matrix[m]]);
    }
Enter fullscreen mode Exit fullscreen mode

It pushes into a new array with [...matrix[m]]

Collapse
 
vipinkumarsn4 profile image
Vipin Saini

Hi Samantha,

Good Post!
Thanks!

Collapse
 
vidhyakrish profile image
vidhyakrish

Array.form() will do the same operations and same behaviour as [...] spread.

for eg.

let nestedArray = [1, [2], 3];
let arrayCopy = Array.from(nestedArray);

// Make some changes
arrayCopy[0] = '👻'; // change shallow element
console.log(nestedArray); //[1,[2],3]

console.log(arrayCopy); // [ '👻',[2],3]
arrayCopy[1][0] = '💩'; // change nested element
console.log(nestedArray); // [ 1, [ '💩' ], 3 ]

Collapse
 
alinalapina profile image
Alina Lapina

In the last clone (recursive code) the function have to return a copy of item, {...item}, in order to create deep cloning, not the item reference.
The whole piece of code is:

const clone = (items) => items.map(item => Array.isArray(item) ? clone(item) : {...item});

Collapse
 
cuterthanbacon profile image
CuterThanBacon

how would you code the recursion example without using map?

Collapse
 
vitalcog profile image
Chad Windham • Edited

If you want/need to not use ES6 stuff you would simply use a standard for loop.


// Old javascript

function cloneDeeply(arrayToClone) {

  var newArray = [];

  for (var i = 0; i < arrayToClone.length; i++) {

    if (arrayToClone[i].isArray) {
      cloneDeeply(arrayToClone[i])
    }
    else {
      newArray.push(arrayToClone[i])
    }
  }

  return newArray;
}

// Then to use it

var clone = cloneDeeply(yourArrayToCloneGoesHere);

I didn't test this or anything btw just writing an example off of the top of my head, so no promises lol

Collapse
 
caseycole589 profile image
Casey Cole

this is the best way to deep copy and you should be cloning html element refs as other people said in the comments that is and easy way to get memory leaks