JavaScript Classes and Prototypal Inheritance
JavaScript classes, introduced in ECMAScript 2015 (ES6), provide a
cleaner and more familiar syntax for object-oriented programming, but
they do not introduce a new object-oriented inheritance model. Instead,
the class syntax acts as “syntactic sugar” over
JavaScript’s existing prototypal inheritance mechanism. Under the hood,
objects still inherit properties and methods directly from other objects
via prototype chains.
Classes Are Functions
At runtime, a JavaScript class is fundamentally a
constructor function. When you declare a class, JavaScript creates a
function with the same name.
class Person {
constructor(name) {
this.name = name;
}
}
console.log(typeof Person); // "function"The constructor method defined inside the class serves
as the actual function body executed when using the new
keyword.
Method Placement on the Prototype
In classical prototypal JavaScript, shared methods are manually
assigned to a function’s prototype object to ensure
instances share a single copy in memory:
function Person(name) {
this.name = name;
}
Person.prototype.greet = function() {
return `Hello, my name is ${this.name}`;
};When using class syntax, any method declared inside the
class body (outside the constructor) is automatically
assigned to Person.prototype:
class Person {
constructor(name) {
this.name = name;
}
greet() {
return `Hello, my name is ${this.name}`;
}
}
console.log(Person.prototype.hasOwnProperty('greet')); // trueWhen an instance calls greet(), the JavaScript engine
checks the instance itself first. If it cannot find it, it traverses up
the internal prototype link ([[Prototype]] or
__proto__) to Person.prototype.
Inheritance with
extends and super
The extends keyword establishes a prototype chain
between two constructor functions and their prototypes.
Consider this subclass:
class Employee extends Person {
constructor(name, title) {
super(name);
this.title = title;
}
work() {
return `${this.name} is working as a ${this.title}`;
}
}Behind the scenes, extends configures two separate
prototype links:
- Instance Prototype Chain: It links
Employee.prototype.[[Prototype]]toPerson.prototype. This allowsEmployeeinstances to access methods defined onPerson.prototype. - Static Prototype Chain: It links
Employee.[[Prototype]]toPerson. This allows static methods defined onPersonto be inherited byEmployee.
The super(name) call invokes the parent constructor
(Person), passing the context of the newly created
Employee instance so properties like this.name
can be initialized.
Key Differences from Pure Constructor Functions
While the class syntax translates to prototypal patterns, the JavaScript engine enforces a few stricter rules:
- Mandatory
new: Classes cannot be invoked withoutnew. CallingPerson()throws aTypeError. - Strict Mode: The body of a class always executes in
strict mode (
'use strict'). - Non-enumerable Methods: Prototype methods defined
inside classes are non-enumerable by default, meaning they will not show
up in
for...inloops orObject.keys(). - No Hoisting: Unlike function declarations, class declarations are not hoisted and cannot be accessed before evaluation.
The class syntax simplifies the creation and maintenance of prototype chains, offering a more declarative structure while relying entirely on JavaScript’s prototype-based object model.