Lodash isPlainObject and Object.create(null)
This article examines how Object.create(null) affects
the evaluation logic inside the Lodash _.isPlainObject
method. Objects created with a null prototype do not
inherit from Object.prototype, leading to distinct
architectural limitations such as the absence of standard utility
methods, the lack of a constructor property, and immediate
termination of the prototype chain. Below is a breakdown of how these
specific prototype constraints impact Lodash’s plain object
detection.
Absence of
Inherited Object.prototype Methods
Objects created via Object.create(null) have no
prototype ([[Prototype]] is null).
Consequently, they do not inherit core methods such as
hasOwnProperty, isPrototypeOf,
toString, or valueOf.
If _.isPlainObject were to invoke any method directly on
the target instance (e.g.,
value.hasOwnProperty('constructor')), JavaScript would
throw a TypeError: value.hasOwnProperty is not a function.
To prevent runtime exceptions, Lodash must:
- Avoid invoking object methods directly from the instance.
- Rely on generic utilities, such as
Object.prototype.hasOwnProperty.call(...)or modern static alternatives likeObject.hasOwn().
Missing constructor
Property
For standard plain objects (such as object literals {}
or those initialized via new Object()),
_.isPlainObject validates whether the object’s constructor
matches the native Object constructor. It performs this by
comparing stringified representations of the constructor function:
Function.prototype.toString.call(Ctor) === ObjectConstructorStringWith Object.create(null), there is no
constructor property anywhere on the prototype chain. If
Lodash relied exclusively on constructor inspection to identify plain
objects, Object.create(null) would fail the check or
evaluate to undefined.
Lodash explicitly resolves this limitation by inspecting the
prototype using Object.getPrototypeOf(value). If the
retrieved prototype is strictly null, Lodash short-circuits
and evaluates the value as true before any constructor
resolution logic is executed:
var proto = Object.getPrototypeOf(value);
if (proto === null) {
return true;
}Broken Prototype Traversal Expectations
Certain plain-object detection patterns (such as those historically used in older libraries or older utility algorithms) traverse upward through the prototype chain using a loop:
while (Object.getPrototypeOf(proto) !== null) {
proto = Object.getPrototypeOf(proto);
}This pattern assumes that all plain objects eventually lead to
Object.prototype, where
Object.getPrototypeOf(Object.prototype) === null. In the
case of Object.create(null), the very first prototype read
returns null.
Any algorithm that assumes proto is non-null after the
first retrieval attempts to access properties on null,
causing an immediate fatal error. Lodash’s implementation avoids this
structural assumption by checking for null at the root
level rather than assuming a multi-tiered prototype chain.
Bypassing Cross-Realm Prototype Discrepancies
In multi-realm environments, such as browser applications with
multiple iframe elements, standard objects have constructor
references tied to their originating window context
(window.Object !== iframe.contentWindow.Object).
Because Object.create(null) instances completely bypass
constructor assignments and prototype linkages, they behave identically
across realms. The prototype evaluates to the universal primitive
null in any execution context. While this eliminates
cross-realm constructor mismatches, it forces
_.isPlainObject to bifurcate its validation path: one path
for objects with a null prototype, and an entirely
separate, more complex path for objects with prototype chains that must
resolve back to a native Object constructor.