There is a difference between copying one and multiple levels of an array in JavaScript. Hence, there are two types of array cloning - shallow and deep. If an original array only contains primitive values (string or number), using shallow clone methods is best. For nested arrays, a deep clone is required. In this article, we will look at examples of shallow and deep copy solutions in JavaScript.

Copying a value type

To understand how array copying actually works, let’s take a look at a simple example in the next code block:

const value = 4;
const valueCopy = value; // copy of value (original array)

console.log(valueCopy); // 4

valueCopy = 20; // change the valueCopy
console.log(valueCopy); // 20

console.log(value); // 4

// original array is the same

We are creating a copy of the original array - valueCopy. If we change the valueCopy, the original value stays the same.

Copying a reference type

Let's copy an array using the same method as we did for a value type.

const array = [1,2,3];
const arrayCopy = array; // copy of array

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

arrayCopy [0] = '20'; // change the first element of our array
console.log(arrayCopy); // [20,2,3]

console.log(array); // [20,2,3]

// original array got affected

As we can see, changing the properties of valueCopy caused the original array value to change as well. It is the case because array objects in JavaScript pass by reference. Therefore, valueCopy contains a reference to the object value.

Both of them refer to the same location in memory. No new object was created within this process. So, let’s find out the different methods of copying array objects in JavaScript.

Ways to copy a reference type

The problem with copying a reference type in the code snippet above is that we copied the pointer instead of the value. So, let us find out how to solve this issue using the different methods for copying an array in JavaScript.

  • With spread (…) operator

const array = [1,2,3];
const arrayCopy1 = [...array]; // TRUE copy of array using spread …

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

arrayCopy1 [0] = '20'; // change the first element of our array
console.log(arrayCopy1); // [20,2,3]

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

// original array is the same

As you can see, the contents of the original array don’t change even after modifying the properties of arrayCopy.

  • With Object.assign()

const array = [1,2,3];
const arrayCopy2 = Object.assign({}, array); // TRUE copy of array using Object.assign()

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

arrayCopy2 [0] = '20';
console.log(arrayCopy2); // [20,2,3]

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

This method is used to shallow copy array objects. With the object literal {}, a new clone array containing all the values from the original array is returned.

  • With Array.from()

const array = [1,2,3];
const arrayCopy3 = Array.from(array); // TRUE copy of array using Array.from()

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

arrayCopy3 [0] = '20';
console.log(arrayCopy3); // [20,2,3]

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

The built-in Array.from()method is the equivalent to the object spread (...) and is good for creating one level copy.

  • With slice() method

const array = [1,2,3];
const arrayCopy4 = array.slice(); // TRUE copy of array using .slice()

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

arrayCopy4 [0] = '20';
console.log(arrayCopy4); // [20,2,3]

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

From the example above, we see that another method to make a shallow copy of a JavaScript array is to use slice().

All these methods don’t work on multi-dimensional arrays. Hence, they just do a copy of one level of nesting - shallow copy. It works good for primitive values, but will fail when it comes to arrays with nested references.

Deep copy vs Shallow copy

So far, all the methods we have looked at do a shallow clone of the array. However, if the array is nested, its objects will be passed by reference. As a result, once we change the nested element, the original array also gets modified. Like this:

const nestedArray = [1,[2],3];
const arrayCopy = [...nestedArray]; // copy of nestedArray

arrayCopy [0] = '20'; // change the first element of our array
arrayCopy [1] [0] = '100'; // change the nested element
console.log(arrayCopy); // ['20',['100'],3]

console.log(nestedArray); // [1,['100'],3]

// nested array got affected

To leave the original array constant, even after modifying nestedArray, we need to use a deep clone.

  • Deep copy with JSON.stringify and JSON.parse

One of the solutions to do a deep array copy is to combine both JSON.stringify and JSON.parse.

const nestedArray = [1,[2],3];
const arrayCopy1 = JSON.parse(JSON.stringify(nestedArray)); // copy of nestedArray with JSON

arrayCopy1 [0] = '20'; // change the first element of our array
arrayCopy1 [1] [0] = '100'; // change the nested element
console.log(arrayCopy1); // [20, 100, 3]

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

// nested array is the same

Combining these two methods, we managed to lose the reference of the nested objects towards its parent array object. JSON.stringify makes a string from a given object with no reference passed. JSON.parse converts this new string to a javascript object. As a result, we have an independent object.

When using a JSON solution for copying nested arrays, keep in mind that it doesn't work with DOM nodes, undefined, JS dates, infinity, NaN, maps, or other complex types within your object. Use a library function in this way:

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

Customized deep cloning

There are two libraries with methods for deep array copying - Lodash and Ramda. To do a copy with the Lodash library, use the cloneDeep() method.

import _ from "lodash" // import Lodash library
let nestedArray = [1,[2],3];
let arrayCopy2 = .cloneDeep(nestedArray); // copy of nestedArray using .cloneDeep()

arrayCopy2 [1] = '100'; // change the nested element
console.log(arrayCopy2); // [1, ['100'], 3]

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

The programming library Ramda includes the R.clone() method for making a deep clone of an array.

import _ from "ramda" // import Ramda library
let nestedArray = [1,[2],3];
let arrayCopy3 = R.clone(nestedArray); // copy of nestedArray using R.clone()

arrayCopy3 [1] = '100'; // change the nested element
console.log(arrayCopy3); // [1, ['100'], 3]

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

This method is equivalent to cloneDeep() in Lodash for several reasons. Firstly, both of them don’t have a shallow copy helper method. Secondly, they work with all types of the contents (function and symbol are copied by reference), while the JSON.stringify and JSON.parse method only works with primitive values (ie. number, string, or object).

Conclusion

Choosing the best way to copy an array should be easier now since we understand how it’s done in JavaScript. Every method has more than one condition on making an independent copied object.

If you have to deal with one level of nesting array, or want to remain the nesting element constant, then simply use shallow copy in JavaScript. Such methods as the spread operator (), slice(), object.assign() work great.

However, if arrays have objects inside them (ie. multi-dimensional arrays), then deep clone methods need to be used. We recommend using cloneDeep() or R.clone(), because this way you shouldn’t worry about the type of the contents, as you do with JSON.stringify and JSON.parse. In addition, these customized deep cloning methods are fast and easy-to-use.