Clone Arrays in JavaScript

In one of my previous articles I covered how you can copy objects in JavaScript. Copying an object is a pretty complicated endeavor, given that you would also have to be able to copy every other data type that could be in the object. But what if you're just copying an array? Like the last article, there are quite a few ways to perform this task, a few of which I'll go over in this article.

But first, a note on speed. While this might not matter for all applications, it's something to consider if copying large arrays is a common operation in your code, or if speed really matters. For some of the methods below I note its speed relative to the other methods, which comes from the results of this benchmark.

Copying Simple Arrays

For this first part, let's assume that the array you want to copy contains only primitive (and immutable) data types. That is, the array only contains numbers, booleans, strings, etc. This way we can focus more on the transfer of data from one array to another, as opposed to how we handle copying the actual contents of the array, which I'll cover in the section "Deep Copies" below.

There are a surprising number of ways to copy an array, some of which include:

  • push
  • Spread
  • slice
  • Array.from
  • _.clone

The spread operator and the slice method are the fastest ways to copy a shallow array, but keep in mind that this does depend on the underlying runtime, so that might not be universally true.

Push

This is probably the most obvious solution, which loops over the original array and uses the new array's push() method to add elements from one array to another:

let oldArr = [3, 1, 5, 2, 9];  
let newArr = [];  
for (let i=0; i < oldArr.length; i++) {  
    newArr.push(oldArr[i]);
}

We simply loop over the array to be copied and push each element to the new array.

Spread

This method uses the spread operator, which was defined in ES6 and is available in most up-to-date browsers.

It works like the following:

let oldArr = [3, 1, 5, 2, 9];  
let newArr = [...oldArr];  

If I'm going to use a native solution and no third-party library then this is typically the solution I prefer thanks to its clean and simple syntax.

One important note is that this copying only works at the top level (like many of these methods), so it should not be used if you need deep copies of anything.

Slice

The slice() method is typically used for returning a portion of an array, specified by the beginning and end parameters. If no parameters are passed, however, then a copy of the entire array is returned:

let oldArr = [3, 1, 5, 2, 9];  
let newArr = oldArr.slice();  

In many JavaScript runtimes this is the fastest way to copy an array.

Array.from

The Array.from method is meant to create a shallow copy of any iterable you pass to it, and it also takes an optional mapping function as the second parameter. So it can be used to create an array out of strings, sets, maps, and of course, other arrays:

let oldArr = [3, 1, 5, 2, 9];  
let newArr = Array.from(oldArr);  

Lodash Clone

Lodash's clone() and cloneDeep() methods may be familiar to you if you read this article on copying objects. The methods do exactly what you'd expect - any object (or array, primitive, etc.) passed to it will be copied and returned.

_.cloneDeep (described further below) is different in that it doesn't stop cloning at the top level, it will recursively copy all objects it encounters at any level.

Given this, we can use it to copy arrays as well:

let oldArr = [3, 1, 5, 2, 9];  
let newArr = _.clone(oldArr);  

_.clone performs very well compared to the other methods, so if you're already using this library in your application then this is a simple solution.

Deep Copies

One important thing to point out is that all of the methods described above only perform shallow copies of your arrays. So if you have an array of objects, for example, the actual array will be copied, but the underlying objects will be passed by reference to the new array.

To demonstrate this problem, let's look at an example:

let oldArr = [{foo: 'bar'}, {baz: 'qux'}];  
let newArr = [...oldArr];  
console.log(newArr === oldArr);  
console.log(newArr[0] === oldArr[0]);  
false  
true  

Here you can see that while the actual array is new, the objects within it were not. For some applications this can be a big problem. If this applies to you then here are some other methods to try.

Lodash Clone Deep

Lodash's _.cloneDeep method does exactly the same thing as _.clone(), except that it recursively clones everything in the array (or object) you pass it. Using the same example as above, we can see that using _.cloneDeep() will provide us with both a new array and copied array elements:

const _ = require('lodash');

let oldArr = [{foo: 'bar'}, {baz: 'qux'}];  
let newArr = _.cloneDeep(oldArr);  
console.log(newArr === oldArr);  
console.log(newArr[0] === oldArr[0]);  
false  
false  

JSON Methods

JavaScript does provide some handy JSON methods that handle converting most JS data types to a string, and then a valid JSON string to a JS object. The respective methods are used as follows:

let oldArr = [{foo: 'bar'}, {baz: 'qux'}];  
let arrStr = JSON.stringify(oldArr);  
console.log(arrStr);

let newArr = JSON.parse(arrStr);  
console.log(newArr);

console.log(newArr === oldArr);  
console.log(newArr[0] === oldArr[0]);  
'[{"foo":"bar"},{"baz":"qux"}]'  
[ { foo: 'bar' }, { baz: 'qux' } ]
false  
false  

This method works great, and it doesn't require any third-party libraries. However, there are two main issues:

  • The data must be serializable and deserializable via JSON
  • Using the JSON methods in this way is much slower than other solutions

So if you have data that can't be serialized to JSON or if speed is important for your application, then this might not be a good solution for you.

Conclusion

In this article I covered a number of ways that you can copy arrays in JavaScript, both using native code as well as a useful third-party library in Lodash. We also looked at the issue of deep cloning arrays and what solutions exist to solve it.

Is there a different method that works best for you? Let us know what you think in the comments.