Understanding Lodash keysIn Prototype Chain Traversal

Lodash's _.keysIn function retrieves all enumerable property names of an object, including both its own and inherited properties along the prototype chain. This article breaks down how _.keysIn inspects objects, walks the underlying prototype (__proto__) chain, and extracts unique property identifiers while maintaining consistency across varying JavaScript environments.

The Distinction: _.keys vs. _.keysIn

In standard JavaScript, Object.keys() returns an array of an object's own enumerable properties. Lodash mirrors this behavior with _.keys.

In contrast, _.keysIn behaves similarly to a for...in loop. It captures not only the enumerable keys residing directly on the target object but also traverses upward through the entire inheritance hierarchy to collect keys defined on its prototype chain.

The Internal Mechanism: baseKeysIn and nativeKeysIn

Under the hood, _.keysIn delegates its task to internal utility functions: primarily baseKeysIn and nativeKeysIn. The implementation balances modern ECMAScript specifications with cross-browser compatibility for edge cases (such as handling non-object primitives, sparse arrays, arguments objects, and symbol-keyed properties).

1. Prototype Chain Traversal

To traverse the inheritance structure, Lodash repeatedly ascends the chain using the standard ECMAScript mechanism Object.getPrototypeOf(object) (the standard equivalent of navigating object.__proto__).

The traversal operates iteratively:

  1. Lodash captures the target object.
  2. It extracts the enumerable keys at the current level.
  3. It updates the reference to the parent prototype via Object.getPrototypeOf(currentObject).
  4. The cycle repeats until the prototype pointer resolves to null (the termination point of Object.prototype).

2. Collecting and De-duplicating Property Identifiers

When traversing multiple prototype layers, child objects often shadow properties defined higher up on ancestor prototypes.

To maintain efficiency and correctness:

Conceptual Implementation

Conceptually, the prototype traversal executed by Lodash resembles the following logic:

function extractKeysIn(object) {
  const result = [];
  const seen = new Set();
  let current = object;

  while (current !== null && current !== undefined) {
    // Read enumerable keys at the current level
    const keys = Object.keys(current);
    for (let i = 0; i < keys.length; i++) {
      const key = keys[i];
      if (!seen.has(key)) {
        seen.add(key);
        result.push(key);
      }
    }
    // Ascend the prototype chain
    current = Object.getPrototypeOf(current);
  }

  return result;
}

Handling Edge Cases

Lodash incorporates safeguards during this traversal: