How Lodash keysIn Retrieves Inherited Keys

This article provides an overview of how the Lodash utility library implements _.keysIn to retrieve all enumerable property names of an object, including those inherited from its prototype chain. It covers the difference between _.keys and _.keysIn, examines the underlying reliance on JavaScript's for...in mechanism, outlines the internal Lodash functions involved, and explains how Lodash handles environment-specific edge cases and object coercion.

Difference Between _.keys and _.keysIn

In Lodash, _.keys and _.keysIn serve distinct purposes when inspecting an object's properties:

const parent = { inheritedProp: 'visible' };
const child = Object.create(parent);
child.ownProp = 'also visible';

_.keys(child);   // ['ownProp']
_.keysIn(child); // ['ownProp', 'inheritedProp']

The Underlying Engine: The for...in Loop

JavaScript's native for...in statement is the primary mechanism that inspects both own and inherited enumerable properties. While methods like Object.keys() or Object.getOwnPropertyNames() deliberately stop at the object itself, for...in traverses the prototype chain automatically until it reaches Object.prototype or null.

Lodash builds _.keysIn on top of this behavior using internal helper methods, primarily baseKeysIn and nativeKeysIn.

Step-by-Step Internal Execution

  1. Input Normalization and Null Checks: When _.keysIn(object) is called, it first checks whether the target object is null or undefined. If so, it immediately returns an empty array [] without throwing a TypeError. If the input is a primitive (such as a string or number), it is coerced into an object wrapper so properties can be read.

  2. Array-Like and Complex Type Identification: Lodash checks whether the target is an array, an arguments object, a typed array, or a buffer. For array-like collections, indexes need to be properly surfaced alongside any custom or inherited properties.

  3. Collection via nativeKeysIn / baseKeysIn: The core collection occurs via an internal routine. In standard modern environments, Lodash runs a routine equivalent to:

    function nativeKeysIn(object) {
      const result = [];
      for (const key in object) {
        result.push(key);
      }
      return result;
    }
  4. Filtering and Edge-Case Sanitization: While for...in gathers inherited keys, plain JavaScript iteration presents several browser and engine quirks that Lodash normalizes:

    • The prototype Property: If the object is a constructor function, iterating over its prototype can produce non-standard results across different JavaScript engines. Lodash checks if the object is a prototype itself and avoids pushing the native constructor key if it is non-enumerable.
    • Shadowed Properties: In older JavaScript engines (such as older V8 or Internet Explorer versions exhibiting the DontEnum bug), certain properties like toString or valueOf were skipped in for...in loops even if explicitly overwritten. Lodash includes fallback checks to ensure that shadowed properties are appropriately identified and captured.
    • Index Keys on Length-Bound Structures: For strings and array-like objects, Lodash guarantees that indices (e.g., '0', '1') are listed consistently before generic object properties.

Through these steps, _.keysIn provides a safe, normalized array containing all accessible enumerable property names across an object and its entire prototype hierarchy.