How do I correctly clone a JavaScript object?

Learn how do i correctly clone a javascript object? with practical examples, diagrams, and best practices. Covers javascript, clone, javascript-objects development techniques with visual explanations.

Mastering JavaScript Object Cloning: Deep vs. Shallow Copies

Illustration of two identical objects, one with nested elements pointing to the same memory location (shallow copy) and another with completely independent nested elements (deep copy).

Learn the essential techniques for cloning JavaScript objects, understanding the critical differences between shallow and deep copies, and choosing the right method for your needs.

In JavaScript, objects are reference types, meaning that when you assign one object to another variable, you're not creating a new, independent copy. Instead, both variables point to the same underlying object in memory. This behavior can lead to unexpected side effects when modifying what you intend to be a 'copy'. Correctly cloning an object is crucial for maintaining data integrity and preventing bugs. This article will guide you through various methods for both shallow and deep cloning, helping you understand their implications and choose the appropriate technique.

Understanding Shallow vs. Deep Cloning

Before diving into specific methods, it's vital to grasp the distinction between shallow and deep cloning. This concept dictates how nested objects and arrays within your primary object are handled during the cloning process.

flowchart TD
    A[Original Object] --> B{Clone Operation}
    B --> C{Shallow Clone}
    B --> D{Deep Clone}

    C --> C1[New Object Reference]
    C1 --> C2["Copies Top-Level Properties (Primitives by Value, Objects by Reference)"]
    C2 --> C3["Nested Objects Share References with Original"]

    D --> D1[New Object Reference]
    D1 --> D2["Copies All Properties Recursively (Primitives & Objects by Value)"]
    D2 --> D3["Nested Objects are Independent Copies"]

    style C fill:#f9f,stroke:#333,stroke-width:2px
    style D fill:#bbf,stroke:#333,stroke-width:2px

Shallow vs. Deep Cloning Process Flow

Shallow Copy: A shallow copy creates a new object and copies all the top-level properties from the original object to the new one. If a property's value is a primitive (like a string, number, or boolean), a new copy of that primitive is made. However, if a property's value is another object (including arrays), only a reference to that nested object is copied. This means that both the original and the shallow-copied object will share the same nested object. Modifying the nested object in one will affect the other.

Deep Copy: A deep copy, on the other hand, creates a completely independent duplicate of the original object. It recursively copies all properties, including nested objects and arrays, ensuring that no references are shared between the original and the cloned object. Changes to any part of the deep-copied object will not affect the original.

Methods for Shallow Cloning

Shallow cloning is often sufficient when your object doesn't contain nested objects or when you explicitly want shared references for nested structures. Here are the most common techniques:

const originalObject = {
  a: 1,
  b: 'hello',
  c: { nested: 'property' },
  d: [1, 2, 3]
};

// Method 1: Spread Syntax (...)
const shallowCopySpread = { ...originalObject };
console.log('Spread Copy:', shallowCopySpread);

// Method 2: Object.assign()
const shallowCopyAssign = Object.assign({}, originalObject);
console.log('Object.assign Copy:', shallowCopyAssign);

// Demonstrating shared reference for nested objects
shallowCopySpread.c.nested = 'modified by spread';
console.log('Original after spread modification:', originalObject.c.nested); // 'modified by spread'

shallowCopyAssign.d.push(4);
console.log('Original array after assign modification:', originalObject.d); // [1, 2, 3, 4]

Shallow cloning using spread syntax and Object.assign()

Methods for Deep Cloning

When you need a truly independent copy, especially with nested objects or arrays, deep cloning is the way to go. This ensures that modifications to the cloned object do not inadvertently affect the original.

const originalObject = {
  a: 1,
  b: 'hello',
  c: { nested: 'property', arr: [5, 6] },
  d: [1, 2, { deep: 'value' }],
  e: new Date(),
  f: function() { console.log('hi'); },
  g: undefined
};

// Method 1: JSON.parse(JSON.stringify())
// Limitations: Does not handle functions, Dates, undefined, RegExp, Maps, Sets, or circular references.
const deepCopyJSON = JSON.parse(JSON.stringify(originalObject));
console.log('JSON Copy:', deepCopyJSON);
console.log('JSON Copy Date:', deepCopyJSON.e); // Date becomes string
console.log('JSON Copy Function:', deepCopyJSON.f); // Function is lost

deepCopyJSON.c.nested = 'modified by JSON';
deepCopyJSON.d[2].deep = 'new deep value';
console.log('Original after JSON modification (nested):', originalObject.c.nested); // 'property'
console.log('Original after JSON modification (deep array):', originalObject.d[2].deep); // 'value'

// Method 2: StructuredClone (Modern Browser API - Node.js v17+)
// Handles more types including Dates, RegExp, Maps, Sets, and circular references.
// Does NOT handle functions.
let deepCopyStructuredClone;
try {
  deepCopyStructuredClone = structuredClone(originalObject);
  console.log('StructuredClone Copy:', deepCopyStructuredClone);
  console.log('StructuredClone Copy Date:', deepCopyStructuredClone.e); // Date object preserved
  console.log('StructuredClone Copy Function:', deepCopyStructuredClone.f); // Function is lost

  deepCopyStructuredClone.c.nested = 'modified by structuredClone';
  console.log('Original after structuredClone modification:', originalObject.c.nested); // 'property'
} catch (e) {
  console.warn('structuredClone not available or failed:', e.message);
}

// Method 3: Custom Recursive Function (for full control and specific types)
function deepClone(obj, hash = new WeakMap()) {
  if (Object(obj) !== obj) return obj; // Primitive values
  if (hash.has(obj)) return hash.get(obj); // Handle circular references

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

  for (const key in obj) {
    if (Object.prototype.hasOwnProperty.call(obj, key)) {
      result[key] = deepClone(obj[key], hash);
    }
  }
  return result;
}

const deepCopyCustom = deepClone(originalObject);
console.log('Custom Deep Copy:', deepCopyCustom);
console.log('Custom Deep Copy Date:', deepCopyCustom.e); // Date object preserved
console.log('Custom Deep Copy Function:', deepCopyCustom.f); // Function preserved (but not executed)

deepCopyCustom.c.nested = 'modified by custom';
console.log('Original after custom modification:', originalObject.c.nested); // 'property'

Deep cloning using JSON serialization, structuredClone, and a custom recursive function

Choosing the Right Cloning Method

The best cloning method depends entirely on your specific use case, the complexity of your object, and the environment you're working in. Consider the following:

flowchart TD
    A[Start: Need to Clone Object?] --> B{Object has Nested Objects/Arrays?}
    B -- No --> C[Use Shallow Copy (Spread/{...}, Object.assign())]
    B -- Yes --> D{Need Independent Nested Copies?}

    D -- No --> C
    D -- Yes --> E{Object contains Functions, Dates, RegExp, Maps, Sets, or Circular Refs?}

    E -- No --> F[Use JSON.parse(JSON.stringify())]
    E -- Yes --> G{Target Environment Supports structuredClone?}

    G -- Yes --> H[Use structuredClone()]
    G -- No --> I[Implement Custom Recursive Deep Clone]

    C --> J[End]
    F --> J
    H --> J
    I --> J

Decision tree for choosing a JavaScript object cloning method