Copying Array in JavaScript: The Definitive Guide

Ever struggled with copying arrays in JavaScript and wondered why your changes affect the original array? You’re not alone. This common issue arises when you attempt to duplicate arrays, only to find that they’ve been manipulating the original data instead. The problem lies in understanding how JavaScript handles array storage and the concept of references. But fear not, there’s a solution that ensures your arrays are copied safely, preserving the integrity of your data.

In this blog, I will show you:

  • Understanding array basics
  • Shallow copy techniques
  • Deep copy strategy
  • Copying arrays containing objects
  • Advanced copy techniques
  • Performance considerations
  • Practical use-cases

By the end of this guide, you’ll be equipped with the knowledge to tackle array copying with confidence. Let’s dive in!

Understanding the basics

Arrays are like the swiss army knife of data structures in JavaScript – versatile and essential. But sometimes, you need to duplicate an array without altering the original. That’s where array cloning comes into play.

JavaScript Array
JavaScript Array

There are two ways to copy arrays or objects in JavaScript.

1. Shallow Copy

A shallow copy in JavaScript duplicates only the top-level properties of an object or array, with nested objects or arrays remaining linked to the original. This means changes to nested elements in the copy can affect the original.

2. Deep Copy

A deep copy, however, creates a completely independent clone of an object or array, including all nested objects and arrays, ensuring that changes to the copy do not impact the original at any level.

 

JavaScript Shallow Copy vs Deep Copy
JavaScript Shallow Copy vs Deep Copy

Shallow copying array in JavaScript

1. Copy an array in JavaScript using spread operator

Let’s start with the basics, shallow copying. Imagine you have an array of your favorite colors , and you want to make a copy. Here’s how you can do it with the spread operator:
let colors = ['Red', 'Green', 'Blue'];
let colorsCopy = [...colors];
console.log(colorsCopy); 
// Output: ["Red", "Green", "Blue"]
Neat, right? The spread operator ... unpacks the elements of our colors array into a new one, giving you a separate copy. It’s perfect for when you have a simple array without nested objects.

2. Copy an array in JavaScript using slice() method

What if you prefer another method to copy an array instead of spread? You can do the same with the JavaScript slice() method. 

Here is an example of copying an array in JavaScript:

let colors = ['Red', 'Green', 'Blue'];
let colorsCopy = colors.slice();
console.log(colorsCopy); 
// Output: ["Red", "Green", "Blue"]
The slice() method is like using a scalpel to precisely duplicate your array. It’s clean, effective, and doesn’t alter the original.

Deep copying arrays in JavaScript

Let’s discuss two methods of copying an object without reference to the source object.

1. Copy object without reference using JSON methods

Now, let’s tackle the heavyweight, deep copying. This is crucial when dealing with arrays containing objects or other arrays. You don’t want changes in the copy to mess with the original, right?Here’s where JSON.stringify() and JSON.parse() come to the rescue:
// Deep copy array of objects without reference using JSON methods
let originalArray = [
  {
    name: 'John',
    age: 30,
    address: {
      city: 'New York'
    }
  }
];

let copiedArray = JSON.parse(JSON.stringify(originalArray));
copiedArray[0].address = { city: 'Los Angeles' };
console.log(originalArray[0].address); // Output:{city: "New York"}
console.log(copiedArray[0].address);   // Output: {city: "Los Angeles"}

We’ve serialized our originalArray into a string and then parsed it back into an array. The result? A deep copy that doesn’t reference the original. Change the copy all you want; the original won’t budge.

But beware, JSON methods have their limitations. They can’t copy functions or circular references.

2. Copy object without reference using structuredClone()

To overcome the JSON circular reference issue, you can use structuredClone() . Here is an example of deep copy without reference:
// Deep copy array of objects using structuredClone() method
let originalArray = [
  {
    name: 'John',
    age: 30,
    address: {
      city: 'New York'
    }
  }
];

let copiedArray = structuredClone(originalArray);
copiedArray[0].address = { city: 'Los Angeles' };
console.log(originalArray[0].address); // Output:{city: "New York"}
console.log(copiedArray[0].address);   // Output: {city: "Los Angeles"}
StructuredClone() is like having a cloning machine. It creates a deep copy, no strings attached. However, keep an eye on browser support and consider polyfills if needed.

Copying arrays containing objects

When you’re dealing with arrays of objects, a shallow copy just won’t cut it. Why? Because objects are reference types, and a shallow copy would only copy the references, not the actual objects. This means changes to the copied array could affect the original array, which is a big no-no in programming.

Here’s how you can deep copy an array of objects:

// Deep copy using JSON methods
const players = [
  { name: 'John', score: 50 },
  { name: 'Sarah', score: 40 }];

const deepCopiedPlayers = JSON.parse(JSON.stringify(players));
deepCopiedPlayers[0].score = 100;

console.log(players[0].score);           // Output: 50
console.log(deepCopiedPlayers[0].score); // Output: 100
In the code above, we used JSON.stringify() to convert our array into a JSON string and then JSON.parse() to parse that string back into a new array. This effectively creates a deep copy of the array, including all nested objects.

Advanced array copy techniques

For more complex scenarios, like multidimensional arrays, you’ll need a more robust solution. A recursive function can help you create a deep copy of each element at every level.

// Deep copy multidimensional arrays 
function deepCopyArray(arr) {
  let copy = [];
  arr.forEach(elem => {
    if (Array.isArray(elem)) {
      copy.push(deepCopyArray(elem));
    } else if (typeof elem === 'object') {
      copy.push(deepCopyObject(elem));
    } else {
      copy.push(elem);
    }
  });
  
  return copy;
}

function deepCopyObject(obj) {
  let tempObj = {};
  for (let [key, value] of Object.entries(obj)) {
    tempObj[key] = (typeof value === 'object') ? deepCopyObject(value) : value;
  }
  
  return tempObj;
}

Here is an example of how to use this function:

// Example multidimensional array
const multiArray = [[1, 2, [3]], 4];
const multiArrayCopy = deepCopyArray(multiArray);

multiArrayCopy[0][2][0] = 'Changed';

console.log(multiArray); // Output: [[1, 2, [3]], 4]
console.log(multiArrayCopy); // Output: [[1, 2, ['Changed']], 4]
The deepCopyArray() function checks each element. If it’s an array, it calls itself recursively. If it’s an object, it calls deepCopyObject() . Object key values are retrieved using the JavaScript Object.entries() method. For other types, it simply copies the value. This ensures a deep copy at every level.

Performance considerations

When it comes to copying arrays in JavaScript, performance considerations are crucial, especially when dealing with large datasets or operations that need to be performed frequently.

JavaScript Shallow vs Deep Copy - Performance Considerations
JavaScript Shallow vs Deep Copy - Performance Considerations

Practical JavaScript code examples and use cases

Before deciding to copy an array, consider if you need a copy. Sometimes, you can work with the original array or use methods that don’t modify the array, like Array.filter() or Array.map(), which return new arrays.

State Management in UI Libraries

In frameworks like React, you often need to copy an array to avoid mutating the state directly, which can lead to bugs and unpredictable behavior.

// React state update with a copied array
this.setState(prevState => ({
  items: [...prevState.items, newItem]
}));

Working with Immutable Data

When working with libraries that enforce immutability, like Redux or Immutable.js, copying arrays becomes a standard practice to ensure that the original data isn’t changed.

// Redux reducer updating state
function myReducer(state = [], action) {
  switch (action.type) {
    case 'ADD_ITEM':
      return [...state, action.payload];
    default:
      return state;
  }
}

Performance Optimization

When performance is a concern, and you need to copy large arrays, consider using Typed Arrays or Web Workers to handle the operation without blocking the main thread.

// Using Typed Arrays for performance
let buffer = new ArrayBuffer(1024);
let intArray = new Uint32Array(buffer);
let copy = new Uint32Array(intArray);

Deep Copying for Nested Arrays

For nested arrays or arrays of objects, you might need a deep copy to ensure that changes to the new array don’t affect the original.

// Deep copy using a recursive function
function deepCopy(arr) {
  if (Array.isArray(arr)) {
    const copy = [];
    for (let i = 0; i < arr.length; i++) {
      copy[i] = deepCopy(arr[i]);
    }
    return copy;
  }
  if (arr !== null && typeof arr === 'object') {
    const copy = {};
    for (const attr in arr) {
      if (arr.hasOwnProperty(attr)) copy[attr] = deepCopy(arr[attr]);
    }
    return copy;
  }
  return arr;
}

The deepCopy function checks if the input is an array or an object and recursively copies each element or property. This ensures that even nested arrays and objects are copied correctly, preventing any unintended side effects from shared references.

These examples illustrate how array copying is applied in different scenarios, emphasizing the importance of choosing the right method for the task at hand to maintain optimal performance and code integrity.

Conclusion

In conclusion, copying arrays in JavaScript is a fundamental skill that can be achieved through various methods, each with its own use cases and considerations. 

We’ve explored the simple `slice()` method for shallow copies, the versatile `concat()` for combining arrays, the spread operator `…` for a more modern approach, and `Array.from()` for creating new array instances from array-like objects. We also delved into deep copying techniques using `JSON.parse()` and `JSON.stringify()` to ensure nested objects are copied correctly. Understanding these methods allows developers to manipulate and manage data effectively within their JavaScript applications. 

Remember, the right method for copying arrays will depend on the specific requirements of your project and the nature of the data you’re working with.

Scroll to Top