Hone logo
Hone
Problems

JavaScript Deep Object Cloning

Creating a deep clone of a JavaScript object is a common and essential task in software development. It allows you to create an entirely independent copy of an object, ensuring that changes to the clone do not affect the original, and vice-versa. This is crucial when you need to manipulate data structures without unintended side effects, particularly in scenarios involving complex nested objects and arrays.

Problem Description

Your task is to implement a JavaScript function called deepClone that takes a single argument, obj, and returns a deep clone of it.

A deep clone means that not only the top-level properties of the object are copied, but also any nested objects or arrays within it are recursively cloned. This ensures that the cloned object is a completely independent entity.

Key Requirements:

  • The function must handle primitive data types (strings, numbers, booleans, null, undefined, symbols) correctly by returning them as is.
  • The function must handle arrays by creating a new array and deep cloning each of its elements.
  • The function must handle plain JavaScript objects by creating a new object and deep cloning each of its properties.
  • The function should handle circular references within the object structure to prevent infinite loops.
  • The function should preserve the constructor property of cloned objects and arrays.

Expected Behavior:

  • Modifying the cloned object or its nested structures should not affect the original object.
  • Modifying the original object should not affect the cloned object.

Edge Cases to Consider:

  • null and undefined inputs.
  • Empty objects and arrays.
  • Objects with various data types as properties (including nested objects, arrays, primitives).
  • Circular references (e.g., an object that directly or indirectly references itself).
  • Objects created with custom constructors (though the challenge primarily focuses on plain objects and arrays).

Examples

Example 1:

const originalObject = {
  name: "Alice",
  age: 30,
  address: {
    street: "123 Main St",
    city: "Anytown"
  },
  hobbies: ["reading", "hiking"]
};

const clonedObject = deepClone(originalObject);

console.log(originalObject === clonedObject); // Expected: false
console.log(originalObject.address === clonedObject.address); // Expected: false
console.log(originalObject.hobbies === clonedObject.hobbies); // Expected: false

originalObject.age = 31;
originalObject.address.city = "Othertown";
originalObject.hobbies.push("swimming");

console.log(clonedObject.age); // Expected: 30
console.log(clonedObject.address.city); // Expected: Anytown
console.log(clonedObject.hobbies); // Expected: ["reading", "hiking"]

Explanation:

The deepClone function creates a completely new object. When originalObject's properties are modified, clonedObject remains unchanged, demonstrating a successful deep clone.

Example 2:

const originalArray = [1, [2, 3], { a: 4 }];
const clonedArray = deepClone(originalArray);

console.log(originalArray === clonedArray); // Expected: false
console.log(originalArray[1] === clonedArray[1]); // Expected: false
console.log(originalArray[2] === clonedArray[2]); // Expected: false

originalArray[0] = 100;
originalArray[1].push(5);
originalArray[2].a = 40;

console.log(clonedArray[0]); // Expected: 1
console.log(clonedArray[1]); // Expected: [2, 3]
console.log(clonedArray[2].a); // Expected: 4

Explanation:

Similar to objects, arrays are also deeply cloned. Changes to the original array or its nested structures do not affect the cloned array.

Example 3 (Circular Reference):

const objA = { name: "A" };
const objB = { name: "B", ref: objA };
objA.ref = objB; // Creating a circular reference

const clonedObjA = deepClone(objA);

console.log(objA.ref === objB); // Expected: true
console.log(clonedObjA.ref === clonedObjA); // Expected: false (The cloned B should reference cloned A)
console.log(clonedObjA.ref.ref === clonedObjA); // Expected: true

Explanation:

The deepClone function must detect and correctly handle the circular reference. The ref property in the cloned object clonedObjA should point to the cloned version of objB, and vice-versa, preventing an infinite recursion.

Constraints

  • The input obj can be any JavaScript value, including primitives, arrays, and plain objects.
  • The implementation should aim for efficiency but prioritize correctness and handling of edge cases. Avoid using external libraries.
  • The function must correctly clone plain objects ({}) and arrays ([]). While handling instances of custom classes might be beneficial, it's not the primary focus for this challenge.
  • The solution should handle nested structures up to a reasonable depth; stack overflow issues from extremely deep recursion might occur with naive recursive solutions if not managed.

Notes

  • Consider using a Map or WeakMap to keep track of objects that have already been cloned to handle circular references.
  • JSON.parse(JSON.stringify(obj)) is a common way to deep clone, but it has limitations (e.g., it doesn't handle functions, undefined, Date objects, RegExp, Map, Set, or circular references). Your solution should go beyond this.
  • Think about how to differentiate between a plain object and an instance of a custom class.
  • The constructor property is important for maintaining the type of cloned arrays and objects.
Loading editor...
javascript