To correctly clone a JavaScript object, you need to choose between shallow and deep cloning, depending on your use case. Here’s a breakdown of methods, their trade-offs, and examples:
1. Shallow Clone
Creates a copy of the object where nested objects/arrays are still referenced (not truly independent).
Methods:
- Spread Operator (
...
):
const original = { a: 1, b: { c: 2 } };
const clone = { ...original };
Object.assign()
:
const clone = Object.assign({}, original);
Limitations:
- Fails for nested objects (changes to
clone.b.c
will affectoriginal.b.c
). - Does not copy prototypes, getters/setters, or non-enumerable properties.
2. Deep Clone
Creates a fully independent copy of the object, including nested structures.
Methods:
JSON.parse(JSON.stringify())
:
const original = { a: 1, b: { c: 2 } };
const clone = JSON.parse(JSON.stringify(original));
Limitations:
- Fails for values like
undefined
,Function
,Symbol
,Date
(converted to string), or circular references. - Drops prototype chain.
structuredClone()
(Modern Browsers/Node.js ≥17):
const clone = structuredClone(original);
Advantages:
- Handles most types (including
Date
,RegExp
,Map
,Set
, etc.). - Supports circular references.
Limitations: - Fails for
Function
,Symbol
, or DOM nodes. - Lodash
_.cloneDeep()
(Library Solution):
import _ from 'lodash';
const clone = _.cloneDeep(original);
- Handles almost all data types, including functions and complex objects.
3. Custom Deep Clone
For edge cases (e.g., preserving prototypes or handling special objects):
function deepClone(obj) {
if (obj === null || typeof obj !== 'object') return obj;
// Handle Date
if (obj instanceof Date) return new Date(obj);
// Handle Array
if (Array.isArray(obj)) return obj.map(deepClone);
// Handle Object
const clone = {};
for (const key in obj) {
if (obj.hasOwnProperty(key)) {
clone[key] = deepClone(obj[key]);
}
}
return clone;
}
const original = { date: new Date(), arr: [1, 2, { a: 3 }] };
const clone = deepClone(original);
4. When to Use Which Method
Method | Use Case | Limitations |
---|---|---|
Shallow Clone | Simple objects (no nested structures) | Fails for nested objects |
JSON.parse/stringify | Quick deep clone for JSON-safe data | Drops functions, dates, circular refs |
structuredClone() | Modern browsers, complex non-function data | No support for functions/Symbols |
Lodash _.cloneDeep | Robust solution for all data types | Requires external library |
Custom Clone | Full control over cloning logic | Time-consuming to implement |
Examples
1. Shallow Clone Pitfall
const original = { a: 1, b: { c: 2 } };
const clone = { ...original };
clone.b.c = 99;
console.log(original.b.c); // 99 (original is modified!)
2. Deep Clone with structuredClone
const original = { date: new Date(), set: new Set([1, 2]) };
const clone = structuredClone(original);
console.log(clone.date instanceof Date); // true
console.log(clone.set.has(1)); // true
3. Circular Reference (structuredClone)
const obj = { a: 1 };
obj.self = obj; // Circular reference
const clone = structuredClone(obj);
console.log(clone.self === clone); // true (works!)
Final Recommendation
- Use
structuredClone()
for most modern use cases. - Use Lodash
_.cloneDeep
for legacy browsers or complex objects with functions. - Use shallow clones only for simple, flat objects.