How Lodash isSymbol Handles Primitive and Object Symbols
The Lodash _.isSymbol method checks whether a given
value is classified as a Symbol primitive or a
Symbol wrapper object. While native JavaScript operators
like typeof struggle to categorize object-wrapped symbols
consistently alongside primitive symbols, Lodash abstracts this
difference away. This article explains how _.isSymbol
functions under the hood, how it distinguishes and verifies both
primitive and object symbols, and why this distinction matters in
JavaScript development.
The Difference Between Primitive and Object Symbols
JavaScript introduces symbols primarily as primitive values:
const primitiveSymbol = Symbol('description');
typeof primitiveSymbol; // 'symbol'However, it is also possible to wrap a symbol into an object using
the Object() wrapper function:
const objectSymbol = Object(Symbol('description'));
typeof objectSymbol; // 'object'Because typeof objectSymbol yields
'object', standard type checks using
typeof value === 'symbol' fail when dealing with boxed
symbol instances.
How Lodash Implements
_.isSymbol
Lodash bridges this gap by combining a primitive type check with an
internal tag inspection. Under the hood, _.isSymbol uses a
two-pronged evaluation:
- Primitive Check: It first verifies if
typeof value === 'symbol'. If this evaluates totrue, the value is confirmed to be a primitive symbol immediately. - Object Wrapper Check: If the primitive check fails,
it evaluates whether the value is "object-like" (non-null and
typeof value === 'object'). For object-like values, it checks the internal[[Class]]tag usingObject.prototype.toString.call(value). If this returns'[object Symbol]', it confirms the value is an instance of a boxedSymbol.
The simplified implementation looks like this:
function isSymbol(value) {
const type = typeof value;
return type === 'symbol' || (
type === 'object' &&
value !== null &&
Object.prototype.toString.call(value) === '[object Symbol]'
);
}Behavior and Usage Examples
Because of this dual-layer verification, _.isSymbol
provides consistent results regardless of how the symbol was
instantiated:
// Primitive Symbol
const primitive = Symbol('test');
_.isSymbol(primitive);
// => true
// Object-wrapped Symbol
const boxed = Object(Symbol('test'));
_.isSymbol(boxed);
// => true
// Non-symbols
_.isSymbol('test');
// => false
_.isSymbol({});
// => falseCross-Realm Reliability
Using Object.prototype.toString.call(value) also ensures
that _.isSymbol works reliably across different execution
contexts, such as multiple iframes or Node.js vm contexts.
In cross-realm environments, instanceof Symbol fails
because the prototype originates from a different global execution
context. By relying on the internal string tag rather than the prototype
chain, _.isSymbol guarantees accurate identification for
both primitive and boxed symbols regardless of their origin realm.