How Lodash forIn Bypasses ES6 Symbol Properties
Lodash’s _.forIn method is designed to iterate over an
object's own and inherited enumerable properties, but it completely
ignores properties keyed by ES6 Symbols. This behavior is intentional,
rooted in the method's strict adherence to standard ECMAScript
for...in semantics and its internal key-enumeration
architecture. This article details the underlying
mechanisms—specifically native property traversal algorithms and the
intentional exclusion of modern reflection APIs—that cause
_.forIn to bypass Symbol-mapped properties.
Reliance on Standard
for...in Semantics
The primary reason _.forIn ignores Symbol keys is that
it models the behavior of JavaScript's native for...in
loop. When the ECMAScript 2015 (ES6) specification introduced the
Symbol primitive, it defined Symbols to represent
non-string object keys intended for private metadata and extension-safe
properties.
Under the ECMAScript specification, standard enumeration
algorithms—such as [[Enumerate]] and operations driving the
native for...in statement—are explicitly restricted to
string-keyed properties. Because Lodash’s _.forIn is built
directly on top of or as a polyfill for this native looping construct,
it automatically adopts the engine's built-in exclusion of Symbol
identifiers.
Internal
Traversal via baseFor and Key Extraction
Under the hood, Lodash implements _.forIn using an
internal iterator, typically baseFor or
baseForOwn, which accepts an iteration function. In
standard execution paths, _.forIn takes one of two
approaches depending on optimization flags and target environments:
- Direct Native Loop: It evaluates the object using a
standard
for (var key in object)construct. In any ES6-compliant engine, this native loop automatically ignores Symbol keys, even if the property descriptor marks the Symbol asenumerable: true. - Internal Key Lists via
keysIn: If the operation maps keys to an array first (as seen in variations like_.forInRight), it relies onbaseKeysIn. This helper crawls the object and its prototype chain collecting string properties viaObject.keys()or traditionalfor...infilters.
Neither execution path incorporates APIs required to reveal Symbol properties.
Deliberate Omission of Symbol Reflection APIs
In JavaScript, Symbol keys cannot be retrieved through general property enumerators; they require dedicated reflection functions. An engine only exposes Symbols if code explicitly invokes:
Object.getOwnPropertySymbols(obj)Reflect.ownKeys(obj)
Lodash includes utilities that retrieve Symbols when explicitly
necessary (for example, internal cloning or assigning routines such as
baseClone and _.merge), where it combines
keys with getOwnPropertySymbols. However,
_.forIn deliberately omits these calls.
Because _.forIn does not traverse prototype chains using
Object.getPrototypeOf() combined with
Object.getOwnPropertySymbols(), Symbol-mapped properties
never enter the iteration pipeline.
Demonstrating the Behavior
The divergence between string keys and Symbol keys in
_.forIn can be observed in basic usage:
const _ = require('lodash');
const symKey = Symbol('id');
const object = {
regularKey: 'visible',
[symKey]: 'hidden'
};
// Ensure the Symbol property is explicitly enumerable
Object.defineProperty(object, symKey, {
enumerable: true,
value: 'still hidden'
});
_.forIn(object, (value, key) => {
console.log(key, value);
});
// Output:
// 'regularKey' 'visible'Even with enumerable set to true, the
Symbol key is omitted. By combining the default filtering of native
for...in loops with an internal implementation that
abstains from calling Object.getOwnPropertySymbols,
_.forIn maintains legacy specification compatibility and
prevents unintended access to Symbol-mapped metadata.