What is the most efficient way to deep clone an object in JavaScript?

Here are more examples and details for each method to deep clone objects in JavaScript:

1. structuredClone() (Modern Approach)

Supports: Objects, Arrays, Dates, RegExp, Maps, Sets, ArrayBuffers, and more.

Example 1: Basic Object

const original = { 
  name: "Alice", 
  hobbies: ["reading", "hiking"], 
  createdAt: new Date() 
};

const clone = structuredClone(original);
console.log(clone.createdAt instanceof Date); // true (not a string!)

Example 2: Circular References

const original = { a: 1 };
original.self = original; // Circular reference

const clone = structuredClone(original);
console.log(clone.self === clone); // true (preserves circularity)

Example 3: Typed Arrays

const buffer = new ArrayBuffer(16);
const original = { buffer };
const clone = structuredClone(original);
console.log(clone.buffer.byteLength); // 16 (copied buffer)

2. JSON.parse(JSON.stringify()) (Legacy Fallback)

Use for: Simple, JSON-safe data.

Example 1: Loses Data Types

const original = { 
  date: new Date(), 
  fn: () => console.log("test") 
};

const clone = JSON.parse(JSON.stringify(original));
console.log(clone.date); // string (e.g., "2024-05-20T12:00:00.000Z")
console.log(clone.fn); // undefined (functions removed)

Example 2: Fails on Circular References

const obj = { a: 1 };
obj.self = obj;
JSON.parse(JSON.stringify(obj)); // Throws "TypeError: Converting circular structure to JSON"

3. Lodash _.cloneDeep() (Third-Party)

Use for: Legacy projects or complex data with broad browser support.

Example 1: Cloning Complex Types

import _ from 'lodash';

const original = { 
  map: new Map([['key', 'value']]), 
  set: new Set([1, 2, 3]) 
};

const clone = _.cloneDeep(original);
console.log(clone.map.get('key')); // "value" (Map preserved)

Example 2: Prototype Inheritance

class Person {
  constructor(name) { this.name = name; }
}
const original = new Person("Alice");
const clone = _.cloneDeep(original);
console.log(clone instanceof Person); // false (prototype chain not copied)

4. Custom Recursive Cloner

Use for: Edge cases (e.g., cloning functions or custom classes).

Example: Basic Implementation

function deepClone(obj, hash = new WeakMap()) {
  if (obj === null || typeof obj !== 'object') return obj;
  if (hash.has(obj)) return hash.get(obj); // Handle circular references

  const clone = Array.isArray(obj) ? [] : {};
  hash.set(obj, clone);

  for (const key in obj) {
    if (obj.hasOwnProperty(key)) {
      clone[key] = deepClone(obj[key], hash);
    }
  }

  return clone;
}

const original = { a: 1, nested: { b: 2 } };
const clone = deepClone(original);
console.log(clone.nested === original.nested); // false (deep clone)

5. Performance Considerations

  • structuredClone() is ~2x faster than JSON.parse(JSON.stringify()) for large objects.
  • Lodash is slower than structuredClone() but handles more edge cases.
  • Avoid cloning massive objects (>1MB) frequently—it can block the main thread.

Summary Table

MethodSpeedHandles Circular RefsPreserves TypesFunctionsDependencies
structuredClone()FastNone
JSON.parse/stringifyMediumNone
Lodash _.cloneDeep()SlowLodash
Custom Recursive ClonerVaries✅ (if implemented)✅ (custom)✅ (custom)None

When to Use What?

  • Modern Apps: Always prefer structuredClone().
  • Legacy Browsers: Use Lodash or JSON.parse/stringify.
  • Specialized Needs: Write a custom cloner (e.g., cloning functions or classes).

Leave a Reply

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