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.