How JavaScript Extends Keyword Works Internally
JavaScript class inheritance using the extends keyword
provides a clean, declarative syntax for building object hierarchies,
but it operates entirely on JavaScript’s existing prototypal inheritance
model. Internally, extends establishes a dual prototype
chain—linking both instance methods and static methods—while
fundamentally altering how instance construction and the
this binding are handled via super().
Understanding this internal mechanics demystifies how ES6 classes
interact with the engine’s underlying prototype architecture.
The Dual Prototype Chain
When a child class extends a parent class, JavaScript creates two distinct prototype linkages under the hood:
Instance Method Inheritance: The child’s prototype object inherits from the parent’s prototype object:
Object.setPrototypeOf(Child.prototype, Parent.prototype); // Child.prototype.__proto__ === Parent.prototypeThis ensures that any method defined on
Parent.prototypeis accessible by instances ofChildvia standard prototype delegation.Static Method Inheritance: The child constructor function itself inherits from the parent constructor function:
Object.setPrototypeOf(Child, Parent); // Child.__proto__ === ParentThis allows static methods and properties declared on
Parentto be directly accessible onChildwithout instantiating an object.
Constructor
Execution and the this Binding
In standard JavaScript constructor functions, the JavaScript engine
creates a new empty object, binds it to this, and then
executes the constructor body. Derived classes (classes using
extends) behave differently:
- Uninitialized
this: In a derived class, thethisbinding is uninitialized upon entering the constructor. Accessingthisbefore callingsuper()throws aReferenceError. - The Role of
super(): Callingsuper(...args)delegates object creation to the parent class. The engine invokes the parent’s[[Construct]]internal method, passing along the arguments as well as a reference tonew.target(which points to the derived class). - Instance Allocation: The base class allocates the
physical memory for the instance object and sets its prototype to
new.target.prototype(Child.prototype). - Returning the Instance: Once the parent constructor
completes, the resulting instance is returned down the inheritance chain
and bound to the derived class’s
thiscontext.
Internal Mechanics with Reflect.construct
The behavior of extends and super() can be
conceptualized using the built-in Reflect.construct API
introduced in ES6:
function Parent(name) {
this.name = name;
}
function Child(name, role) {
// Equivalent to super(name)
const self = Reflect.construct(Parent, [name], Child);
self.role = role;
return self;
}
// Establish prototype linkages
Object.setPrototypeOf(Child.prototype, Parent.prototype);
Object.setPrototypeOf(Child, Parent);In this flow: - Reflect.construct(Parent, [name], Child)
invokes Parent to initialize the object, but sets the
internal [[Prototype]] of the created object to
Child.prototype. - The derived constructor receives the
initialized object, modifies it, and returns it.
Method Resolution
and super Property Access
When using super.method() inside a derived class method,
the engine uses the internal slot [[HomeObject]].
Every method defined inside an ES6 class possesses a
[[HomeObject]] reference that points to the class prototype
where the method was defined. When super.method() is
evaluated, the JavaScript engine looks up
Object.getPrototypeOf([[HomeObject]]) to find the parent
method and then executes it using the current this context
via .call(this, ...args). This static binding guarantees
that super references resolve correctly even when methods
are detached or dynamically assigned.