How Lodash cloneDeep Handles Getters and Setters
Lodash’s _.cloneDeep method does not preserve accessor
descriptors like getters and setters when duplicating JavaScript
objects. Instead, it invokes the getter function during the traversal
process, copies the resulting value as a standard static data property,
and completely discards any associated setter. This article explains the
technical mechanics behind this behavior, outlines the risks it
introduces to application logic, and provides practical alternatives for
preserving property descriptors.
The Mechanics of
_.cloneDeep
When _.cloneDeep traverses an object, it reads
enumerable own properties using standard value retrieval (equivalent to
source[key]). Because it reads the property value directly
rather than inspecting its descriptor via
Object.getOwnPropertyDescriptor(), JavaScript automatically
executes the getter.
The result returned by the getter is then cloned and assigned to the
new object as a standard data descriptor
({ value: result, writable: true, enumerable: true, configurable: true }).
Any setter logic bound to that key is ignored entirely.
const user = {
firstName: 'Jane',
lastName: 'Doe',
get fullName() {
return `${this.firstName} ${this.lastName}`;
},
set fullName(value) {
[this.firstName, this.lastName] = value.split(' ');
}
};
const clone = _.cloneDeep(user);
console.log(Object.getOwnPropertyDescriptor(clone, 'fullName'));
// Output: { value: 'Jane Doe', writable: true, enumerable: true, configurable: true }Key Impacts on Applications
Loss of Dynamic Behavior and Reactivity
Once cloned, the property ceases to be dynamic. Updatingclone.firstNamewill no longer updateclone.fullName, becausefullNameis now a frozen string value rather than an active function.Triggering Unexpected Side Effects
If a getter contains logging, network requests, or state-mutating operations, cloning the object triggers those side effects unintentionally simply by reading the property.Loss of Setter Validation and Transformations
Assigning a new value to the cloned property performs a standard property assignment. Any validation, data normalization, or dependent state updates defined in the original setter are lost.Potential Infinite Loops
If a getter dynamically instantiates and returns a new object on every access without memoization, deep-cloning utilities can get trapped in recursive loops, leading to memory exhaustion or stack overflow errors.
Preserving Getters and Setters
If preserving accessors is required, you must bypass standard value
retrieval and copy property descriptors explicitly using
Object.getOwnPropertyDescriptors() and
Object.defineProperties().
function cloneWithDescriptors(obj) {
if (obj === null || typeof obj !== 'object') {
return obj;
}
const descriptors = Object.getOwnPropertyDescriptors(obj);
return Object.create(Object.getPrototypeOf(obj), descriptors);
}For nested structures requiring both descriptor preservation and deep
cloning, you must implement a custom recursive cloning function that
checks descriptor.get and descriptor.set. If
accessors are present, define them directly on the target object instead
of recursively traversing their evaluated values.