GitHub

deepClone()

Deep clones any value including objects, arrays, maps, sets, dates, regexps, and handles circular references.

Core Function
Cloning
Memory
Syntax
deepClone(value, cache)
Parameters

value
any

The value to deep clone.

cache
WeakMap
optional

Internal cache for circular reference handling (automatically managed).

Returns

any
- Deep cloned value.

Examples

Basic Object Cloning

const original = {
  name: "John",
  age: 30,
  address: {
    street: "123 Main St",
    city: "New York"
  }
};

const cloned = deepClone(original);

// Modify the clone
cloned.name = "Jane";
cloned.address.city = "Boston";

console.log(original.name); // "John" (unchanged)
console.log(original.address.city); // "New York" (unchanged)
console.log(cloned.name); // "Jane"
console.log(cloned.address.city); // "Boston"

Array Cloning

const originalArray = [
  1, 
  [2, 3], 
  { name: "test", values: [4, 5] }
];

const clonedArray = deepClone(originalArray);

// Modify nested elements
clonedArray[1][0] = 999;
clonedArray[2].name = "modified";
clonedArray[2].values.push(6);

console.log(originalArray[1][0]); // 2 (unchanged)
console.log(originalArray[2].name); // "test" (unchanged)
console.log(originalArray[2].values); // [4, 5] (unchanged)

console.log(clonedArray[1][0]); // 999
console.log(clonedArray[2].name); // "modified"
console.log(clonedArray[2].values); // [4, 5, 6]

Date and RegExp Cloning

const original = {
  createdAt: new Date('2023-01-01'),
  pattern: /[a-z]+/gi,
  data: "some data"
};

const cloned = deepClone(original);

// Dates are properly cloned
console.log(cloned.createdAt instanceof Date); // true
console.log(cloned.createdAt === original.createdAt); // false
console.log(cloned.createdAt.getTime() === original.createdAt.getTime()); // true

// RegExp are properly cloned
console.log(cloned.pattern instanceof RegExp); // true
console.log(cloned.pattern === original.pattern); // false
console.log(cloned.pattern.source === original.pattern.source); // true
console.log(cloned.pattern.flags === original.pattern.flags); // true

Map and Set Cloning

const originalMap = new Map([
  ['key1', { value: 1 }],
  ['key2', { value: 2 }]
]);

const originalSet = new Set([
  { id: 1, name: 'item1' },
  { id: 2, name: 'item2' }
]);

const clonedMap = deepClone(originalMap);
const clonedSet = deepClone(originalSet);

// Maps are deeply cloned
console.log(clonedMap instanceof Map); // true
console.log(clonedMap === originalMap); // false
console.log(clonedMap.get('key1') === originalMap.get('key1')); // false
console.log(clonedMap.get('key1').value); // 1

// Sets are deeply cloned
console.log(clonedSet instanceof Set); // true
console.log(clonedSet === originalSet); // false
console.log(clonedSet.size); // 2

Circular Reference Handling

// Create object with circular reference
const parent = { name: "parent" };
const child = { name: "child", parent: parent };
parent.child = child; // Circular reference

// deepClone handles circular references safely
const clonedParent = deepClone(parent);

console.log(clonedParent.name); // "parent"
console.log(clonedParent.child.name); // "child"
console.log(clonedParent.child.parent === clonedParent); // true (maintains circular structure)
console.log(clonedParent === parent); // false (different objects)

// Verify the circular structure is preserved
console.log(clonedParent.child.parent.child === clonedParent.child); // true

Primitive Values

// Primitive values are returned as-is
console.log(deepClone(42)); // 42
console.log(deepClone("hello")); // "hello"
console.log(deepClone(true)); // true
console.log(deepClone(null)); // null
console.log(deepClone(undefined)); // undefined

// Functions are returned as-is (not cloned)
const func = () => "test";
console.log(deepClone(func) === func); // true

Complex Nested Structure

const complexObject = {
  id: 1,
  metadata: {
    created: new Date(),
    tags: new Set(['tag1', 'tag2']),
    config: new Map([
      ['theme', 'dark'],
      ['lang', 'en']
    ])
  },
  items: [
    {
      id: 1,
      data: { values: [1, 2, 3] }
    },
    {
      id: 2,
      pattern: /test/g,
      nested: {
        deep: {
          value: "deeply nested"
        }
      }
    }
  ]
};

const cloned = deepClone(complexObject);

// All nested structures are properly cloned
console.log(cloned.metadata.created instanceof Date); // true
console.log(cloned.metadata.tags instanceof Set); // true
console.log(cloned.metadata.config instanceof Map); // true
console.log(cloned.items[1].pattern instanceof RegExp); // true

// Original remains unchanged when clone is modified
cloned.items[0].data.values.push(4);
cloned.metadata.tags.add('tag3');

console.log(complexObject.items[0].data.values); // [1, 2, 3] (unchanged)
console.log(complexObject.metadata.tags.size); // 2 (unchanged)

Performance Considerations

// For large objects, consider the performance impact
const largeObject = {
  data: new Array(10000).fill(0).map((_, i) => ({
    id: i,
    value: Math.random(),
    nested: { deep: { value: i } }
  }))
};

console.time('deepClone');
const cloned = deepClone(largeObject);
console.timeEnd('deepClone');

// For frequent cloning, consider alternatives for simple objects
const simpleClone = JSON.parse(JSON.stringify(simpleObject)); // Faster but limited
const deepCloned = deepClone(simpleObject); // More robust but slower

Advanced Use Cases

Cloning Class Instances
class User {
  constructor(name, email) {
    this.name = name;
    this.email = email;
    this.createdAt = new Date();
  }

  getDisplayName() {
    return `${this.name} <${this.email}>`;
  }
}

const originalUser = new User("John Doe", "john@example.com");
originalUser.preferences = { theme: "dark", lang: "en" };

const clonedUser = deepClone(originalUser);

// Properties are cloned
console.log(clonedUser.name); // "John Doe"
console.log(clonedUser.preferences.theme); // "dark"
console.log(clonedUser.createdAt instanceof Date); // true

// Note: Methods are not preserved in cloned objects
console.log(typeof clonedUser.getDisplayName); // "undefined"
console.log(clonedUser instanceof User); // false

// For class instances, consider custom cloning:
class UserWithClone extends User {
  clone() {
    const cloned = new UserWithClone(this.name, this.email);
    cloned.createdAt = new Date(this.createdAt);
    cloned.preferences = deepClone(this.preferences);
    return cloned;
  }
}
Handling Functions in Objects
const objectWithFunctions = {
  data: { count: 0 },
  increment: function() { this.data.count++; },
  decrement: () => { this.data.count--; },
  computed: {
    get doubled() { return this.data.count * 2; }
  }
};

const cloned = deepClone(objectWithFunctions);

// Functions are preserved as-is (not cloned)
console.log(typeof cloned.increment); // "function"
console.log(cloned.increment === objectWithFunctions.increment); // true

// Data is properly cloned
cloned.data.count = 5;
console.log(objectWithFunctions.data.count); // 0 (unchanged)
console.log(cloned.data.count); // 5

// For objects with methods, consider a factory pattern:
function createCounter(initialValue = 0) {
  const state = { count: initialValue };
  
  return {
    data: deepClone(state),
    increment() { this.data.count++; },
    decrement() { this.data.count--; },
    clone() { return createCounter(this.data.count); }
  };
}
Custom Object Types
// Custom objects with special properties
const customObject = {
  buffer: new ArrayBuffer(16),
  view: new DataView(new ArrayBuffer(16)),
  typedArray: new Uint8Array([1, 2, 3, 4]),
  error: new Error("Custom error"),
  promise: Promise.resolve("value"),
  symbol: Symbol("unique"),
  weakMap: new WeakMap(),
  weakSet: new WeakSet()
};

const cloned = deepClone(customObject);

// ArrayBuffer and TypedArrays are cloned
console.log(cloned.buffer instanceof ArrayBuffer); // true
console.log(cloned.buffer === customObject.buffer); // false
console.log(cloned.typedArray instanceof Uint8Array); // true
console.log(Array.from(cloned.typedArray)); // [1, 2, 3, 4]

// Error objects are cloned
console.log(cloned.error instanceof Error); // true
console.log(cloned.error.message); // "Custom error"
console.log(cloned.error === customObject.error); // false

// Symbols are preserved
console.log(cloned.symbol === customObject.symbol); // true

// WeakMap and WeakSet are cloned as empty
console.log(cloned.weakMap instanceof WeakMap); // true
console.log(cloned.weakSet instanceof WeakSet); // true

Common Pitfalls

Memory Leaks with Large Objects
// ❌ Avoid: Cloning very large objects repeatedly
const largeObject = {
  data: new Array(1000000).fill(0).map((_, i) => ({ id: i, value: Math.random() }))
};

// This creates a new 1M element array each time
function processData(obj) {
  const cloned = deepClone(obj); // Memory intensive!
  // ... process cloned data
  return cloned;
}

// ✅ Better: Clone only what you need
function processDataEfficiently(obj) {
  const workingCopy = {
    ...obj,
    data: obj.data.slice(0, 100) // Only clone subset
  };
  // ... process working copy
  return workingCopy;
}

// ✅ Or use shallow clone for simple modifications
function processDataShallow(obj) {
  const shallowCopy = { ...obj };
  shallowCopy.metadata = { ...obj.metadata }; // Clone only nested objects that change
  return shallowCopy;
}
Prototype Chain Loss
class Vehicle {
  constructor(type) {
    this.type = type;
  }
  
  start() {
    return `${this.type} started`;
  }
}

class Car extends Vehicle {
  constructor(brand, model) {
    super("car");
    this.brand = brand;
    this.model = model;
  }
  
  getInfo() {
    return `${this.brand} ${this.model}`;
  }
}

const myCar = new Car("Toyota", "Camry");
const clonedCar = deepClone(myCar);

// ❌ Prototype methods are lost
console.log(typeof clonedCar.start); // "undefined"
console.log(typeof clonedCar.getInfo); // "undefined"
console.log(clonedCar instanceof Car); // false

// ✅ Solution: Custom clone method or factory
class CarWithClone extends Car {
  clone() {
    const cloned = new CarWithClone(this.brand, this.model);
    // Copy any additional properties
    Object.keys(this).forEach(key => {
      if (key !== 'brand' && key !== 'model' && key !== 'type') {
        cloned[key] = deepClone(this[key]);
      }
    });
    return cloned;
  }
}

Best Practices

When to Use Deep Clone
// ✅ Good use cases:
// 1. Immutable data operations
function updateUserPreferences(user, newPrefs) {
  const updatedUser = deepClone(user);
  updatedUser.preferences = { ...updatedUser.preferences, ...newPrefs };
  return updatedUser;
}

// 2. State management
class StateManager {
  constructor(initialState) {
    this.state = deepClone(initialState);
    this.history = [deepClone(initialState)];
  }
  
  setState(newState) {
    this.state = deepClone(newState);
    this.history.push(deepClone(newState));
  }
  
  undo() {
    if (this.history.length > 1) {
      this.history.pop();
      this.state = deepClone(this.history[this.history.length - 1]);
    }
  }
}

// 3. API response caching
class ApiCache {
  constructor() {
    this.cache = new Map();
  }
  
  set(key, data) {
    this.cache.set(key, deepClone(data)); // Prevent external mutations
  }
  
  get(key) {
    const cached = this.cache.get(key);
    return cached ? deepClone(cached) : null; // Return safe copy
  }
}

// ❌ Avoid deep cloning for:
// - Simple primitive values
// - Objects that don't need mutation protection
// - Performance-critical code paths
// - Objects with complex prototype chains
Performance Optimization
// ✅ Optimize cloning performance
class OptimizedCloner {
  constructor() {
    this.cache = new WeakMap();
  }
  
  // Selective cloning based on object type
  smartClone(obj) {
    if (obj === null || typeof obj !== 'object') {
      return obj;
    }
    
    // Check cache first
    if (this.cache.has(obj)) {
      return this.cache.get(obj);
    }
    
    // Handle different object types efficiently
    if (Array.isArray(obj)) {
      return this.cloneArray(obj);
    }
    
    if (obj instanceof Date) {
      return new Date(obj);
    }
    
    if (obj instanceof RegExp) {
      return new RegExp(obj);
    }
    
    // For plain objects, use deepClone
    return deepClone(obj);
  }
  
  cloneArray(arr) {
    // For arrays with primitives, use slice
    if (arr.every(item => typeof item !== 'object' || item === null)) {
      return arr.slice();
    }
    
    // For complex arrays, use deepClone
    return deepClone(arr);
  }
  
  // Batch cloning with shared cache
  cloneBatch(objects) {
    return objects.map(obj => this.smartClone(obj));
  }
}

// Usage
const cloner = new OptimizedCloner();
const cloned = cloner.smartClone(complexObject);

Comparison with Alternatives

deepClone vs JSON.parse(JSON.stringify())
const testObject = {
  date: new Date(),
  regex: /test/g,
  func: () => "hello",
  undefined: undefined,
  symbol: Symbol("test"),
  circular: null
};
testObject.circular = testObject;

// JSON method limitations
try {
  const jsonCloned = JSON.parse(JSON.stringify(testObject));
  console.log(jsonCloned.date instanceof Date); // false (becomes string)
  console.log(jsonCloned.regex instanceof RegExp); // false (becomes {})
  console.log(typeof jsonCloned.func); // "undefined" (lost)
  console.log('undefined' in jsonCloned); // false (lost)
  console.log('symbol' in jsonCloned); // false (lost)
  // Circular reference would throw error
} catch (error) {
  console.log("JSON method failed:", error.message);
}

// deepClone handles all cases
const deepCloned = deepClone(testObject);
console.log(deepCloned.date instanceof Date); // true
console.log(deepCloned.regex instanceof RegExp); // true
console.log(typeof deepCloned.func); // "function"
console.log(deepCloned.circular === deepCloned); // true (circular preserved)

// Performance comparison
const largeObject = { data: new Array(10000).fill({ value: Math.random() }) };

console.time('JSON method');
for (let i = 0; i < 1000; i++) {
  JSON.parse(JSON.stringify(largeObject));
}
console.timeEnd('JSON method');

console.time('deepClone');
for (let i = 0; i < 1000; i++) {
  deepClone(largeObject);
}
console.timeEnd('deepClone');
deepClone vs Lodash cloneDeep
// Comparison with popular libraries
const _ = require('lodash'); // If available

const complexObject = {
  map: new Map([['key', { nested: 'value' }]]),
  set: new Set([{ item: 1 }, { item: 2 }]),
  buffer: new ArrayBuffer(8),
  date: new Date(),
  circular: null
};
complexObject.circular = complexObject;

// Lodash cloneDeep
const lodashCloned = _.cloneDeep(complexObject);
console.log(lodashCloned.map instanceof Map); // true
console.log(lodashCloned.set instanceof Set); // true
console.log(lodashCloned.circular === lodashCloned); // true

// Lopos deepClone
const loposCloned = deepClone(complexObject);
console.log(loposCloned.map instanceof Map); // true
console.log(loposCloned.set instanceof Set); // true
console.log(loposCloned.circular === loposCloned); // true

// Both handle most cases similarly, but deepClone:
// - Has no external dependencies
// - Smaller bundle size
// - Handles some edge cases differently

// Choose based on your needs:
// - Use deepClone for lightweight, dependency-free projects
// - Use Lodash if you're already using it for other utilities
// - Use JSON method for simple objects without special types