What is a shallow copy vs deep copy?
When duplicating objects or arrays in JavaScript, it's essential to understand the distinction between a shallow copy and a deep copy. This difference primarily relates to how nested objects and arrays are handled during the copying process, impacting whether the new copy shares references with the original or creates entirely independent structures.
Shallow Copy
A shallow copy creates a new object or array, but it does not recursively copy nested objects or arrays. Instead, it copies references to those nested structures. This means that if the original object contains a nested object, the shallow copy will have a reference to the same nested object, not a new, independent copy of it.
Common methods for creating a shallow copy include: spread syntax (...), Object.assign(), Array.prototype.slice(), and Array.from().
const originalObject = {
a: 1,
b: {
c: 2
}
};
// Shallow copy using spread syntax
const shallowCopy = { ...originalObject };
console.log(shallowCopy);
// Output: { a: 1, b: { c: 2 } }
// Modify the nested object in the shallow copy
shallowCopy.b.c = 3;
console.log(originalObject.b.c);
// Output: 3 (The original object's nested property was also changed)
In the example above, changing a property within the nested object of the shallow copy also affects the original object because both originalObject.b and shallowCopy.b refer to the exact same nested object in memory.
Deep Copy
A deep copy creates a completely independent duplicate of an object or array, including all nested objects and arrays. This means that all values are copied, not just references. Any modifications made to the deep copy will not affect the original object, and vice versa.
JavaScript does not have a built-in function specifically for deep cloning, but common approaches include using JSON.parse(JSON.stringify(object)) for simple cases or third-party libraries.
const originalObject = {
a: 1,
b: {
c: 2
}
};
// Deep copy using JSON methods
const deepCopy = JSON.parse(JSON.stringify(originalObject));
console.log(deepCopy);
// Output: { a: 1, b: { c: 2 } }
// Modify the nested object in the deep copy
deepCopy.b.c = 3;
console.log(originalObject.b.c);
// Output: 2 (The original object remains unchanged)
Here, modifying deepCopy.b.c does not affect originalObject.b.c because a new, separate object was created for b during the deep copy process.
Key Differences and Use Cases
- Independence: Deep copies are fully independent; shallow copies share references to nested objects.
- Performance: Shallow copies are generally faster and consume less memory as they only copy top-level values and references.
- Complexity: Deep copying can be more complex, especially with circular references or specific data types.
- Use Cases for Shallow Copy: When you only need to copy the top-level structure and don't mind shared references for nested objects, or when nested objects are immutable.
- Use Cases for Deep Copy: When you need a completely separate duplicate of an object, ensuring that changes to the copy do not affect the original, crucial for state management or complex data manipulation.
Comparison of Copy Types
| Feature | Shallow Copy | Deep Copy |
|---|---|---|
| Nested Objects | References shared | New objects created |
| Independence | Partial | Full |
| Performance | Faster | Slower (more complex) |
| Common Methods | Spread {...}, Object.assign(), slice() | JSON.parse(JSON.stringify()), Libraries (e.g., lodash.cloneDeep), structuredClone() |
| Effect of Modification | Modifying nested objects affects original | Modifying nested objects does NOT affect original |
Limitations of JSON.parse(JSON.stringify())
While JSON.parse(JSON.stringify(object)) is a convenient method for deep copying simple objects, it has significant limitations:
It cannot handle circular references, throwing an error.
It loses functions, undefined values, Map, Set, Date objects (converted to strings), RegExp, and other non-JSON-serializable types.
It converts BigInt values to numbers, potentially losing precision.
Libraries for Robust Deep Copying
For more complex scenarios, especially when dealing with non-JSON-serializable data types or circular references, using a dedicated library or modern built-in solutions is recommended.
lodash.cloneDeep(): A widely used and robust function from the Lodash library that handles various data types, including functions, dates, and circular references (with options).structuredClone(): A built-in browser (and Node.js 17+) method for deep cloning values that are transferable between realms. It handles many complex types, including circular references, but has its own set of limitations (e.g., cannot clone functions, DOM nodes, or error objects).