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

  1. Loss of Dynamic Behavior and Reactivity
    Once cloned, the property ceases to be dynamic. Updating clone.firstName will no longer update clone.fullName, because fullName is now a frozen string value rather than an active function.

  2. 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.

  3. 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.

  4. 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.