How do I correctly clone a JavaScript object?

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 affect original.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

MethodUse CaseLimitations
Shallow CloneSimple objects (no nested structures)Fails for nested objects
JSON.parse/stringifyQuick deep clone for JSON-safe dataDrops functions, dates, circular refs
structuredClone()Modern browsers, complex non-function dataNo support for functions/Symbols
Lodash _.cloneDeepRobust solution for all data typesRequires external library
Custom CloneFull control over cloning logicTime-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.

Leave a Reply

Your email address will not be published. Required fields are marked *