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:
- Lodash captures the target object.
- It extracts the enumerable keys at the current level.
- It updates the reference to the parent prototype via
Object.getPrototypeOf(currentObject). - The cycle repeats until the prototype pointer resolves to
null(the termination point ofObject.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:
- Enumerability Checks: Only properties whose
property descriptors mark
enumerable: trueare recorded. Non-enumerable built-ins (likeObject.prototype.toString) are ignored. - Shadowing Management: If a property name has already been registered from a child level, the inherited property with the same key is skipped. This guarantees that each identifier appears only once in the resulting array.
- Fast-Path Optimizations: In engines where native
for...inbehavior is reliable, Lodash leverages native loops directly withinnativeKeysInto allow the JavaScript engine's internal C++ implementation to traverse the hidden classes and prototype links at maximum speed.
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:
- Array-Like Objects & Buffers: Lodash normalizes
index properties for TypedArrays, Strings, and Node.js
Bufferinstances so index keys are predictably treated as strings. - Objects without Prototypes: Objects created via
Object.create(null)do not have a__proto__link. The traversal safely terminates after inspecting the base object without throwing reference errors. - Prototype Properties: When an object's prototype
itself is inspected directly, Lodash prevents the non-enumerable
constructorproperty from leaking into the output array.