cloneDeep is a function provided by the Lodash library in JavaScript, which allows for deep cloning of objects and arrays. This means it creates a complete copy of a given value, including nested objects or arrays, ensuring that any changes to the cloned value do not affect the original.
https://bigfrontend.dev/problem/create-cloneDeep
Object.assign()
could be used to do shallow copy, while for recursive deep copy, _.cloneDeep could be very useful.
Can you create your own _.cloneDeep()
?
The lodash implementation actually covers a lot of data types, for simplicity, your code just need to cover
- primitive types and their wrapper Object
- Plain Objects (Object literal) with all enumerable properties
- Array
Write a function that creates a deep clone of a value. The function should handle primitive types, Object literal and Array.
First, check the data type of the source data, return the source if it is a primitive or null
. Depending the data type of the source, either create an empty array or an empty object to store the cloned value. Get all enumerable properties of the source including all symbol properties. Loop over the properties. For each property, recursively call the function with property value as argument; create the property in the cloned object/array and set its value to the result of the recursive call.
🙋♀️🙋♂️ In the initial attempt, I didn't handle the circular reference. We can use a WeakMap
to store the source and its copy. If the source object is already in the WeakMap
, return its corresponding copy instead of recursing further.
// Use WeakMap that stores cloned results to handle circular reference.
const cachedResult = new WeakMap();
function cloneDeep(data) {
if (data === null || data === undefined) {
return data;
}
if (typeof data !== 'object') {
return data;
}
// If the source object is already in the WeakMap,
// its corresponding copy is returned instead of recursing
// further.
if (cachedResult.has(data)) {
return cachedResult.get(data);
}
const clone = Array.isArray(data) ? [] : {};
// Store the source object and its clone in the WeakMap.
cachedResult.set(data, clone);
const keys = [
...Object.getOwnPropertyNames(data),
...Object.getOwnPropertySymbols(data),
];
for (const key of keys) {
clone[key] = cloneDeep(data[key]);
}
return clone;
}
Let's break down the selected code:
-
const cachedResult = new WeakMap();
This line creates a new WeakMap that will be used to store cloned objects. This is used to handle circular references in the object being cloned. -
function cloneDeep(data) {...}
This is the main function that performs a deep clone of an object. It takes an object as input and returns a new object that is a deep copy of the input. -
Inside
cloneDeep
the function first checks if the input is null or not an object. If it is, the input is returned as is. -
Then, it checks if the input is already in the
cachedResult
WeakMap. If it is, the function returns the cloned object from the WeakMap instead of cloning it again. This is how the function handles circular references. -
If the input is not in the
cachedResult
WeakMap, the function creates a new object or array to be the clone, depending on whether the input is an array or not. -
The function then stores the input and its clone in the
cachedResult
WeakMap. -
The function gets all the own property names and symbols of the input, and for each one, it sets the corresponding property of the clone to be a deep clone of the property of the input. This is done recursively using
cloneDeep
-
Finally, the function returns the clone.
const array = [1, 2, 3, 4, 5];
const obj = {
a: 1,
b: {
c: 2,
d: 3,
},
};
const circular = {
a: 1,
};
circular.circular = circular;
const deep = {
a: 1,
b: {
c: 2,
d: 3,
},
e: {
f: {
g: 4,
},
},
};
const arrayClone = cloneDeep(array);
const objClone = cloneDeep(obj);
const circularClone = cloneDeep(circular);
const deepClone = cloneDeep(deep);
console.log(arrayClone); // [1, 2, 3, 4, 5]
console.log(objClone); // { a: 1, b: { c: 2, d: 3 } }
console.log(circularClone); // { a: 1, circular: [Circular] }
console.log(deepClone); // { a: 1, b: { c: 2, d: 3 }, e: { f: { g: 4 } } }
// The cloned object has a different reference.
console.log(array !== arrayClone); // true
console.log(obj !== objClone); // true
console.log(circular !== circularClone); // true
console.log(deep !== deepClone); // true
// The nested object has a different reference.
console.log(obj.b !== objClone.b); // true
console.log(deep.b !== deepClone.b); // true
console.log(deep.e !== deepClone.e); // true
console.log(deep.e.f !== deepClone.e.f); // true
// The circular reference is handled correctly.
console.log(circular === circular.circular); // true
console.log(circularClone === circularClone.circular); // true
// The cloned object has the same value.
console.log(array.toString() === arrayClone.toString()); // true
console.log(JSON.stringify(obj) === JSON.stringify(objClone)); // true
console.log(circular === circular.circular); // true
console.log(JSON.stringify(deep) === JSON.stringify(deepClone)); // true
// The cloned object has the same structure.
console.log(Object.keys(obj).toString() === Object.keys(objClone).toString()); // true
console.log(Object.keys(deep).toString() === Object.keys(deepClone).toString()); // true
// The cloned object has the same nested structure.
console.log(
Object.keys(obj.b).toString() === Object.keys(objClone.b).toString()
); // true
console.log(
Object.keys(deep.b).toString() === Object.keys(deepClone.b).toString()
); // true
console.log(
Object.keys(deep.e).toString() === Object.keys(deepClone.e).toString()
); // true
console.log(
Object.keys(deep.e.f).toString() === Object.keys(deepClone.e.f).toString()
); // true
// The cloned object has the same value.
console.log(obj.b.c === objClone.b.c); // true
console.log(deep.b.c === deepClone.b.c); // true
console.log(deep.e.f.g === deepClone.e.f.g); // true