Keywords: ES6 Class Inheritance | super() Call | JavaScript Inheritance Mechanism
Abstract: This article provides a comprehensive exploration of the mandatory requirement to call super() in ES6 class inheritance. It explains from the ECMAScript specification perspective why subclass constructors must invoke super(), analyzes the initialization process of this binding, and illustrates exception behaviors through code examples. By referencing sections 8.1.1.3.4 and 9.2.2 of the ES2015 specification, the article details the GetThisBinding mechanism of function environment records and the [[Construct]] internal method, offering developers a thorough understanding of JavaScript class inheritance mechanisms.
With the introduction of class syntax in ES6 (ECMAScript 2015), inheritance mechanisms brought clearer object-oriented programming patterns but also introduced new constraints. One critical constraint is that in derived class (subclass) constructors, the super() method must be called; otherwise, a runtime exception occurs. This requirement is not arbitrary but is based on strict specifications in the ECMAScript standard regarding the initialization process of this binding.
The Mandatory Nature of super() Calls
Consider the following ES6 class inheritance example:
class Character {
constructor(){
console.log('invoke character');
}
}
class Hero extends Character{
constructor(){
super(); // must be called
console.log('invoke hero');
}
}
var hero = new Hero();
If the super() call is omitted, the code throws an exception: ReferenceError: this is not defined. This occurs because in the ES6 class inheritance model, this in the subclass constructor remains in an "uninitialized" state until super is called.
ECMAScript Specification Analysis
The ES2015 specification defines this behavior through two key sections:
First, section 8.1.1.3.4 defines the GetThisBinding method of function environment records. For class constructors, this can be in an "uninitialized" state. When attempting to access this while it is uninitialized, the specification requires throwing an exception. This explains why using this before calling super() results in an error.
Second, section 9.2.2 describes the behavior of the [[Construct]] internal method. When a function is called via new or super:
- For base class constructors,
thisis initialized at step #8 of[[Construct]] - For derived class constructors,
thisremains uninitialized untilsuper()is explicitly called - At the end of the construction process,
GetThisBindingis called; ifthisis still uninitialized, an exception is thrown
Technical Implementation Details
From an implementation perspective, ES6 class inheritance requires subclass constructors to either:
- Call
super()before accessingthisto initialize thethisbinding - Or explicitly return an object as a replacement for
this
The following code demonstrates the exception scenario:
class Hero extends Character {
constructor() {
// No super() call, this is uninitialized
console.log(this); // Throws ReferenceError
}
}
The correct approach is to always call super() at the beginning of the subclass constructor:
class Hero extends Character {
constructor(name) {
super(); // Initialize this
this.name = name; // Now safe to use this
}
}
Alternatives and Considerations
Although the specification mandates calling super(), there is one special case: if the constructor explicitly returns an object, it can avoid calling super(). However, this approach is not recommended as it breaks the normal inheritance chain:
class Hero extends Character {
constructor() {
// No super() call, but returns a custom object
return { custom: 'object' };
}
}
In practical development, the following best practices should always be followed:
- Call
super()on the first line of the subclass constructor - Pass necessary parameters to
super() - Access
thisonly after thesuper()call - Avoid returning custom objects from constructors (unless specifically required)
This design ensures consistency and type safety in JavaScript class inheritance. While it adds coding constraints, it provides a more reliable foundation for object-oriented programming.