Shallow vs. Deep Copying in JavaScript

Share this article

Shallow vs. Deep Copying in JavaScript

Copying and modifying objects in JavaScript is never as simple as it seems. Understanding how objects and references work during this process is essential for web developers and can save hours of debugging. This becomes increasingly important when you work with large stateful applications like those built in React or Vue.

Shallow copying and deep copying refer to how we make copies of an object in JavaScript and what data is created in the ‘copy’. In this article, we’ll delve into the distinctions between these methods, explore their real-world applications, and uncover the potential pitfalls that can emerge when using them.

What is ‘Shallow’ Copying

Shallow copying refers to the process of creating a new object that is a copy of an existing object, with its properties referencing the same values or objects as the original. In JavaScript, this is often achieved using methods like Object.assign() or the spread syntax ({...originalObject}). Shallow copying only creates a new reference to the existing objects or values and doesn’t create a deep copy, which means that nested objects are still referenced, not duplicated.

Let’s look at the following code example. The newly created object shallowCopyZoo is created as a copy of zoo via the spread operator, which has caused some unintended consequences.

let zoo = {
  name: "Amazing Zoo",
  location: "Melbourne, Australia",
  animals: [
    {
      species: "Lion",
      favoriteTreat: "🥩",
    },
    {
      species: "Panda",
      favoriteTreat: "🎋",
    },
  ],
};

let shallowCopyZoo = { ...zoo };
shallowCopyZoo.animals[0].favoriteTreat = "🍖";
console.log(zoo.animals[0].favoriteTreat); 
// "🍖", not "🥩"

But let’s look at what is really in shallowCopyZoo. The properties name and location are primitive values (string), so their values are copied. However, the animals property is an array of objects, so the reference to that array is copied, not the array itself.

You can quickly test this (if you don’t believe me) using the strict equality operator (===). An object is only equal to another object if the refer to the same object (see Primitive vs. Reference data types). Notice how the property animals is equal on both but the objects themselves are not equal.

console.log(zoo.animals === shallowCopyZoo.animals)
// true

console.log(zoo === shallowCopyZoo)
// false

This can lead to potential issues in code bases and make life especially hard when working with large Modifying a nested object in the shallow copy also affects the original object and any other shallow copies, as they all share the same reference.

Deep Copying

Deep copying is a technique that creates a new object, which is an exact copy of an existing object. This includes copying all its properties and any nested objects, instead of references. Deep cloning is helpful when you need two separate objects that don’t share references, ensuring changes to one object don’t affect the other.

Programmers often use deep cloning when working with application state objects in complex applications. Creating a new state object without affecting the previous state is crucial for maintaining the application’s stability and implementing undo-redo functionality properly.

How to deep copy using JSON.stringify() and JSON.parse()

A popular and library-free way of deep copying is to use the built in JSON stringify() and parse() methods.

The parse(stringify()) method is not perfect. For example, special data types like Date will be stringified and undefined values will be ignored. Like all options in this article, it should be considered for your individual use case.

In the code below, we’ll create a deepCopy function these methods to deep clone an object. We then copy the playerProfile object and modify the copied object without affecting the original one. This showcases the value of deep copying in maintaining separate objects without shared references.

const playerProfile = {
  name: 'Alice',
  level: 10,
  achievements: [
    {
      title: 'Fast Learner',
      emoji: '🚀'
    },
    {
      title: 'Treasure Hunter',
      emoji: '💰'
    }
  ]
};

function deepCopy(obj) {
  return JSON.parse(JSON.stringify(obj));
}

const clonedProfile = deepCopy(playerProfile);

console.log(clonedProfile);
/* Output:
{
  name: 'Alice',
  level: 10,
  achievements: [
    {
      title: 'Fast Learner',
      emoji: '🚀'
    },
    {
      title: 'Treasure Hunter',
      emoji: '💰'
    }
  ]
}
*/

// Modify the cloned profile without affecting the original profile
clonedProfile.achievements.push({ title: 'Marathon Runner', emoji: '🏃' });
console.log(playerProfile.achievements.length); // Output: 2
console.log(clonedProfile.achievements.length); // Output: 3

Libraries for Deep Copying

There are also a variety of third-party libraries that offer a deep copying solution.

A Vanilla JS Deep Copy Function

If for some reason you do not want to use the JSON object or a third party library, you can also create a custom deep copy function in vanilla JavaScript. that recursively iterates through the object properties and creates a new object with the same properties and values.

const deepCopy = (obj) => {
    if (typeof obj !== 'object' || obj === null) {
        return obj;
    }

    const newObj = Array.isArray(obj) ? [] : {};

    for (const key in obj) {
        newObj[key] = deepCopy(obj[key]);
    }

    return newObj;
}

const deepCopiedObject = deepCopy(originalObject);

Downsides of Deep Copying

While deep copying offers great benefits for data accuracy, it’s recommended to evaluate whether deep copying is necessary for each specific use case. In some situations, shallow copying or other techniques for managing object references might be more suitable, providing better performance and reduced complexity.

  1. Performance impact: Deep copying can be computationally expensive, especially when dealing with large or complex objects. As the deep copy process iterates through all nested properties, it may take a significant amount of time, negatively impacting the performance of your application.
  2. Memory consumption: Creating a deep copy results in the duplication of the entire object hierarchy, including all nested objects. This can lead to increased memory usage, which may be problematic, particularly in memory-constrained environments or when dealing with large data sets.
  3. Circular references: Deep copying can cause issues when objects contain circular references (i.e., when an object has a property that refers back to itself, directly or indirectly). Circular references can lead to infinite loops or stack overflow errors during the deep copy process, and handling them requires additional logic to avoid these issues.
  4. Function and special object handling: Deep copying may not handle functions or objects with special characteristics (e.g., Date, RegExp, DOM elements) as expected. For example, when deep copying an object containing a function, the function’s reference might be copied, but the function’s closure and its bound context will not be duplicated. Similarly, objects with special characteristics might lose their unique properties and behavior when deep copied.
  5. Implementation complexity: Writing a custom deep copy function can be complex, and built-in methods like JSON.parse(JSON.stringify(obj)) have limitations, such as not handling functions, circular references, or special objects correctly. While there are third-party libraries like Lodash’s _.cloneDeep() that can handle deep copying more effectively, adding an external dependency for deep copying might not always be ideal.

Conclusion

Thanks for taking the time to read this article. Shallow vs. Deep copying is surprisingly more complex than any first timer imagines. Although there are a lot of pitfalls in each approach, taking the time to review and consider the options will ensure your application and data remains exactly how you want it to be.

Frequently Asked Questions (FAQs) about Shallow vs Deep Copying in JavaScript

What is the main difference between shallow and deep copying in JavaScript?

The primary difference between shallow and deep copying lies in the way they handle properties that are objects. In a shallow copy, the copied object shares the same reference to the nested objects as the original. This means that changes made to the nested objects will reflect in both the original and copied objects. On the other hand, a deep copy creates a new instance of the nested objects, meaning changes made to the nested objects in the copied object will not affect the original object.

How does the spread operator work in shallow copying?

The spread operator (…) in JavaScript is commonly used for shallow copying. It copies all enumerable own properties from one object to another. However, it only copies the properties at the first level and references for nested objects. Therefore, changes to nested objects will affect both the original and copied objects.

Can I use JSON methods for deep copying?

Yes, you can use JSON methods for deep copying in JavaScript. The combination of JSON.stringify() and JSON.parse() methods can create a deep copy of an object. JSON.stringify() converts the object into a string, and JSON.parse() parses the string back into a new object. However, this method has limitations as it does not copy methods and does not work with special JavaScript objects like Date, RegExp, Map, Set, etc.

What are the limitations of shallow copying?

Shallow copying only copies the properties at the first level and references for nested objects. Therefore, if the original object has nested objects, changes to these nested objects will affect both the original and copied objects. This can lead to unexpected results and bugs in your code.

How does the Object.assign() method work in shallow copying?

The Object.assign() method is used to copy the values of all enumerable own properties from one or more source objects to a target object. It returns the target object. However, it performs a shallow copy, meaning it only copies the properties at the first level and references for nested objects.

What is the best way to deep copy an object in JavaScript?

The best method to deep copy an object in JavaScript depends on the specific requirements of your code. If your object does not contain methods or special JavaScript objects, you can use the combination of JSON.stringify() and JSON.parse() methods. For more complex objects, you might need to use a library like Lodash, which provides a deep clone function.

Can I use the spread operator for deep copying?

No, the spread operator in JavaScript only performs a shallow copy. It copies the properties at the first level and references for nested objects. To perform a deep copy, you need to use other methods or libraries.

What are the performance implications of deep copying?

Deep copying can be more resource-intensive than shallow copying, especially for large objects. This is because deep copying creates new instances for all nested objects, which can take up more memory and processing power.

How does deep copying handle circular references?

Deep copying methods like JSON.stringify() and JSON.parse() do not handle circular references and will throw an error. A circular reference occurs when an object’s property references the object itself. To handle circular references, you need to use a library that supports it, like Lodash.

Why should I care about the difference between shallow and deep copying?

Understanding the difference between shallow and deep copying is crucial for managing data in JavaScript. It affects how your objects interact with each other. Shallow copying can lead to unexpected results and bugs if you’re not careful, as changes to nested objects affect both the original and copied objects. On the other hand, deep copying ensures that your copied objects are completely independent of the original objects.

Mark O'NeillMark O'Neill
View Author

Mark is the General Manager of SitePoint.com. He loves to read and write about technology, startups, programming languages, and no code tools.

Share this article
Read Next
Get the freshest news and resources for developers, designers and digital creators in your inbox each week