Understanding JavaScript Prototype Chain Through the ECMAScript Specification
This article delves into the ECMAScript specification to precisely explain how JavaScript's prototype chain and property lookup work, covering essential internal methods like [[Get]], property descriptors, reference records, and the role of the Receiver parameter, with illustrative code examples and diagrams.
Preface
In the previous chapter we covered a large set of basic concepts that are essential for understanding the ECMAScript specification. Readers are strongly encouraged to read the previous chapter before proceeding.
This article examines the prototype chain from the perspective of the specification. While many classic books and articles explain the prototype chain, most of them provide a summarized mental model. Here we ask whether JavaScript's internal property‑lookup rules truly match those summaries, or whether they are merely a convenient abstraction.
We will answer this question by analysing the specification directly, using English terms where necessary for precision.
Example
First, an example to illustrate how a property is looked up on an object.
const foo = { name1: 'grace' };
const bar = { name2: 'walk' };
Object.setPrototypeOf(foo, bar);
foo.name2 // walkWe define foo and set its prototype to bar . Accessing foo.name2 retrieves the name2 property from bar .
In everyday understanding, JavaScript walks up the prototype chain until it finds the property or reaches null , returning undefined if the property is not found.
Object Property Lookup Process
essential internal method — [[Get]]
The specification defines property lookup as an essential internal method . Every object must implement algorithms for these methods, though the concrete algorithms may differ between ordinary and exotic objects.
the essential internal methods used by this specification that are applicable to all objects created or manipulated by ECMAScript code. Every object must have algorithms for all of the essential internal methods. However, all objects do not necessarily use the same algorithms for those methods.
The term "essential internal methods" (基础内部方法) describes behaviours such as [[Get]] . Both ordinary and exotic objects have implementations for these methods, but the implementations can vary.
When evaluating foo.name2 , the engine invokes the [[Get]] internal method.
The specification lists several sections that define [[Get]] . Section 10.1.8 describes the algorithm for ordinary objects; other sections describe exotic objects such as arguments objects, integer‑indexed exotic objects, module namespace exotic objects, and Proxy exotic objects.
Thus [[Get]] has a one‑to‑many relationship with its concrete implementations.
Definition of [[Get]]
In section 10.1.8 the algorithm is defined as:
Return ? OrdinaryGet(O, P, Receiver).
The result of the call is the outcome of OrdinaryGet(O, P, Receiver) . In our example, O is foo , P is name2 , and Receiver is also foo (explained later).
OrdinaryGet Steps
The steps of OrdinaryGet are illustrated in the following diagram:
Let desc be ? O.[[GetOwnProperty]](P).
If desc is a Property Descriptor, the algorithm proceeds to steps 3‑7; otherwise it moves to step 2.
Property Descriptor
A Property Descriptor describes the composition and behaviour of an object property. It can be either a Data Descriptor or an Accessor Descriptor.
// data descriptor
{ [[value]]: ..., [[Writable]]: ..., [[Enumerable]]: ..., [[Configurable]]: ... }
// accessor descriptor
{ [[Get]]: ..., [[Set]]: ..., [[Enumerable]]: ..., [[Configurable]]: ... }Calling Object.getOwnPropertyDescriptor() returns a JavaScript representation of this descriptor.
Execution Flow
If desc is a Data Descriptor, step 3 returns [[Value]] . If it is an Accessor Descriptor, steps 4‑7 invoke the getter function.
If [[GetOwnProperty]] returns undefined , step 2 obtains the prototype via [[GetPrototypeOf]] and recursively calls [[Get]] on the prototype. The recursion continues until a property is found or the prototype becomes null , at which point undefined is returned.
The overall process matches the common mental model: walk up the prototype chain until the property is found or the chain ends.
Going Deeper
We now explore when the essential internal method is invoked and where the Receiver argument originates.
Syntax Definition
The specification defines a MemberExpression grammar rule. For an expression like foo.name2 , the rule MemberExpression . IdentifierName applies.
The runtime semantics for this production involve several steps; step 4 calls EvaluatePropertyAccessWithIdentifierKey , which ultimately returns a Reference Record:
{
[[Base]]: baseValue,
[[ReferencedName]]: propertyNameString,
[[Strict]]: strict,
[[ThisValue]]: empty,
}Reference Record and GetValue
A Reference Record is a specification‑only type that records the base object, the property name, strictness, and the this value. The abstract operation GetValue(V) extracts the actual value from a Reference Record.
For foo.name2 the Reference Record is:
{
[[Base]]: foo,
[[ReferencedName]]: name2,
[[Strict]]: strict,
[[ThisValue]]: empty,
}When GetValue processes this record, it ultimately calls baseObj.[[Get]](P, GetThisValue(V)) . The Receiver argument is the result of GetThisValue(V) , which in this case is the original foo object.
Another example demonstrates why this inside a getter refers to the original object, not the prototype:
const foo = { name: 'grace' };
const bar = { name: 'walk', get getName() { return this.name; } };
Object.setPrototypeOf(foo, bar);
console.log(foo.getName); // graceThe Receiver is passed unchanged during the prototype‑chain traversal, so this.name resolves to foo.name ("grace").
Runtime Semantics: Evaluation
Step 1 of the MemberExpression evaluation returns a Reference Record for the identifier foo via ResolveBinding , which searches lexical environments. Steps 1‑2 therefore locate the binding of foo before the property access proceeds.
Conclusion
This chapter used the prototype chain as a case study to dissect the exact algorithmic steps JavaScript follows when looking up object properties. We introduced essential internal methods, Property Descriptors, Reference Records, and the role of the Receiver argument. These concepts recur throughout the ECMAScript specification and will appear again in later chapters.
References
ECMAScript Specification
Understanding the ECMAScript spec, part 2
Thank you for your support – a single click to like, share, or follow motivates further updates.
Rare Earth Juejin Tech Community
Juejin, a tech community that helps developers grow.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.