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:
- 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.
- Inspect the Prototype: If the property is not found
directly on the object, the engine accesses the object’s internal
[[Prototype]]link (accessible programmatically viaObject.getPrototypeOf()or__proto__). - 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]]. - Reach the Terminal Prototype: The base object for
standard objects is
Object.prototype. If the property is still not found, the engine checksObject.prototype. - Terminate at
null: The prototype ofObject.prototypeisnull. Reachingnullmarks the end of the chain. Because no further objects exist to inspect, the engine resolves the operation by returningundefined.
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 -> undefinedWhen 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.