Fundamentals 15 min read

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.

Rare Earth Juejin Tech Community
Rare Earth Juejin Tech Community
Rare Earth Juejin Tech Community
Understanding JavaScript Prototype Chain Through the ECMAScript Specification

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 // walk

We 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); // grace

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

JavaScriptInternal MethodsPrototype ChainECMAScript Specificationproperty descriptorReference Record
Rare Earth Juejin Tech Community
Written by

Rare Earth Juejin Tech Community

Juejin, a tech community that helps developers grow.

0 followers
Reader feedback

How this landed with the community

login Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.