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:

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) === ObjectConstructorString

With 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.