JavaScript Prototype Chain Property Lookup

JavaScript uses a prototypal inheritance model where objects inherit properties and methods directly from other objects. When code attempts to access a property on an object, the JavaScript engine executes an internal lookup algorithm that traverses the prototype chain step-by-step until the property is found or the end of the chain is reached. This article details the exact mechanics of this lookup process, how property shadowing occurs, and the role of the terminal null reference.

The Internal [[Get]] Operation

When you read a property on an object (for example, user.name), the JavaScript runtime invokes the internal [[Get]] method on that object. The lookup proceeds through the following sequential steps:

  1. Check Own Properties: The engine first checks whether the target object itself directly contains the property (known as an “own property”). If it exists, the search terminates immediately, and the value or the return value of its getter is returned.
  2. Inspect the Prototype: If the property is not found directly on the object, the engine accesses the object’s internal [[Prototype]] link (accessible programmatically via Object.getPrototypeOf() or __proto__).
  3. Traverse Upward: The engine repeats the check on the prototype object. If the prototype contains the property, its value is returned. If not, the engine moves up to that prototype’s own [[Prototype]].
  4. Reach the Terminal Prototype: The base object for standard objects is Object.prototype. If the property is still not found, the engine checks Object.prototype.
  5. Terminate at null: The prototype of Object.prototype is null. Reaching null marks the end of the chain. Because no further objects exist to inspect, the engine resolves the operation by returning undefined.

Code Example of Chain Traversal

Consider the following hierarchy:

const grandParent = { origin: "Global" };
const parent = Object.create(grandParent);
parent.role = "Admin";

const child = Object.create(parent);
child.name = "Alex";

console.log(child.name);   // Found on 'child' -> "Alex"
console.log(child.role);   // Not on 'child', found on 'parent' -> "Admin"
console.log(child.origin); // Not on 'child' or 'parent', found on 'grandParent' -> "Global"
console.log(child.age);    // Traversed to null -> undefined

When evaluating child.origin, the engine checks child, then parent, then grandParent, where it finds a match and stops further traversal.

Property Shadowing

If an object contains a property with the exact same name as a property higher up on its prototype chain, the object’s own property takes precedence. This behavior is called property shadowing. The engine satisfies the lookup at the earliest point in the chain, masking any identically named properties further up.

const vehicle = { wheels: 4 };
const motorcycle = Object.create(vehicle);
motorcycle.wheels = 2; // Shadows vehicle.wheels

console.log(motorcycle.wheels); // 2 (resolved on motorcycle directly)

Methods and the this Context

Methods are simply properties holding functions. When a method is inherited and resolved through the prototype chain, the execution context (this) always points to the object on which the method was originally invoked, not the prototype object where the function definition resides.

const proto = {
  identify() {
    return `ID: ${this.id}`;
  }
};

const item = Object.create(proto);
item.id = 42;

console.log(item.identify()); // "ID: 42"

During item.identify(), JavaScript resolves identify via proto, but executes it with this referencing item.

Lookup vs. Assignment

Property lookup rules apply strictly to reading values. When setting a property (obj.prop = value), the engine typically creates or updates the property directly on obj itself rather than modifying the prototype, preventing accidental mutations to shared prototype states.