How Lodash _.has Checks for Direct Properties

In JavaScript, verifying property ownership on an object is a frequent necessity, and the Lodash library provides the _.has method specifically to determine if a path is a direct property of an object. This article examines the internal mechanics of _.has, how it safely verifies property ownership via Object.prototype.hasOwnProperty, how it processes nested path strings and arrays, and how it differs from prototype-inclusive checks like _.hasIn.

The Core Mechanism: Direct Ownership via hasOwnProperty

The primary purpose of _.has(object, path) is to verify that a property belongs directly to an object rather than being inherited through its prototype chain.

Native JavaScript provides Object.prototype.hasOwnProperty, but calling it directly on an object (such as obj.hasOwnProperty(prop)) can lead to runtime exceptions if the object was created with Object.create(null) or if the hasOwnProperty method has been overwritten. Lodash circumvents these risks by safely invoking the method using Object.prototype.hasOwnProperty.call(object, key) via its internal utilities.

const proto = { inheritedProp: 'value' };
const obj = Object.create(proto);
obj.directProp = 'value';

_.has(obj, 'directProp');     // true
_.has(obj, 'inheritedProp');  // false

Deep Path Traversal

Unlike native property checks, _.has natively supports deep property paths represented either as dot-notation strings (e.g., 'user.profile.name') or arrays of keys (e.g., ['user', 'profile', 'name']).

When checking deep paths, Lodash processes the input through the following steps:

  1. Path Normalization: The path is converted into an array of segments using an internal toPath parser.
  2. Sequential Verification: Lodash traverses the object structure one segment at a time.
  3. Safety Checks: At each segment, Lodash checks if the current target is an object and whether the current segment exists directly on that target.
  4. Early Termination: If any intermediate segment evaluates to null, undefined, or does not exist as a direct property, traversal halts immediately and the method returns false, preventing TypeError exceptions.
const user = {
  profile: {
    details: {
      id: 101
    }
  }
};

_.has(user, 'profile.details.id');     // true
_.has(user, ['profile', 'details']);    // true
_.has(user, 'profile.settings.theme');  // false (safely returns false)

_.has vs. _.hasIn

Understanding how _.has functions requires distinguishing it from Lodash’s counterpart method, _.hasIn.

function Person() {}
Person.prototype.species = 'Homo sapiens';

const individual = new Person();
individual.name = 'Alice';

_.has(individual, 'species');    // false (inherited from prototype)
_.hasIn(individual, 'species');  // true  (found on prototype chain)

Through this architecture, _.has delivers a defensive, fail-safe mechanism to validate direct property existence across shallow and deeply nested data structures.