JavaScript, the ubiquitous language of the web, has long been a source of intrigue for developers, particularly when it comes to object-oriented programming (OOP
) concepts. While most OOP languages rely on classes as the cornerstone of inheritance, JavaScript takes a unique approach, utilizing prototypes to establish relationships between objects.
This often leads to confusion among developers, as JavaScript does, in fact, have classes. But what exactly is the distinction between class-based inheritance and prototypical inheritance in JavaScript? Delving into this realm will unravel the intricacies of JavaScript's inheritance mechanism and empower developers to master this fundamental concept.
Object Oriented Programming vs. JavaScript
In the realm of programming, objects
are often described as instances of classes. This textbook definition, while accurate, can leave beginners perplexed. To simplify this concept, imagine classes as blueprints for real-world entities. Just as architects create blueprints for buildings, programmers use classes to design the structure of objects.
JavaScript, despite its widespread use, is not an object-oriented language in the traditional sense. However, it does support object-oriented principles through a mechanism called prototypal inheritance. This concept, while initially confusing, offers greater flexibility and power compared to classical inheritance.
At the core of prototypal inheritance lies the prototype chain
, a hierarchical structure where objects inherit properties and methods from their prototypes. The Object
prototype, located at the top of this chain, serves as the foundation for all JavaScript objects.
While ES6 introduced the class
keyword, it's important to understand that this is merely syntactic sugar, a way to organize code in a more class-like fashion. JavaScript's underlying prototype-based nature remains unchanged.
To grasp the essence of prototypal inheritance, consider the following analogy: Imagine a family tree, where each person inherits traits and characteristics from their parents. Similarly, in JavaScript, objects inherit properties and methods from their prototypes.
This hierarchical relationship enables code reuse and promotes modularity. Developers can create base objects with common properties and methods, and then create child objects that inherit these traits while adding their own unique characteristics.
Build your intuition. Fill in the missing part by typing it in.
Objects can be created with the ____ method.
Write the missing line below.
Class-based inheritance and the Weak Base Class problem
The introduction of the class
keyword in ES6 marked a significant shift in JavaScript, allowing developers to more naturally implement an object-oriented approach. With the extends
keyword, JavaScript introduced a streamlined way to inherit properties and methods between classes. However, this approach is not without its challenges, one of which is the Weak Base Class
problem.
Consider a scenario with three classes: Animal
, Bird
, and Penguin
. In this hierarchy, Animal
is the base class, Bird
extends Animal
, and Penguin
extends Bird
. This structure might initially seem straightforward - Penguin
inherits from Bird
, which in turn inherits from Animal
. However, this chain of inheritance can introduce complexity and unexpected behavior, as Penguin
implicitly inherits from Animal
through Bird
.
To better understand this, let's delve into a concrete example:
1class Animal {
2 constructor(name) {
3 this.name = name;
4 }
5
6 eat() {
7 console.log(`${this.name} is eating.`);
8 }
9}
10
11class Bird extends Animal {
12 constructor(name, kind) {
13 super(name);
14 this.kind = kind;
15 }
16
17 fly() {
18 console.log(`${this.name}, a ${this.kind}, is flying.`);
19 }
20}
21
22class Penguin extends Bird { // Here lies the Weak Base Class problem
23 constructor(name) {
24 super(name, "penguin");
25 }
26
27 swim() {
28 console.log(`${this.name} is swimming, not flying.`);
29 }
30}
In this example, Penguin
inherits from Bird
, which in turn inherits from Animal
. While this inheritance chain seems logical, it can lead to issues if the methods in Animal
are not compatible with the behaviors of Penguin
. For instance, if Bird
has a method fly()
, it's not relevant for a Penguin
, which can't fly.
To tackle this issue, developers might need to introduce an intermediate class or refactor the existing classes. For example, they could introduce a FlightlessBird
class between Bird
and Penguin
to better represent the behaviors of flightless birds.
xxxxxxxxxx
console.log(penguin.fly());
class Animal {
constructor(name) {
this.name = name;
}
eat() {
console.log(`${this.name} is eating.`);
}
}
class Bird extends Animal {
constructor(name, kind) {
super(name);
this.kind = kind;
}
fly() {
console.log(`${this.name}, a ${this.kind}, is flying.`);
}
}
class Penguin extends Bird { // Here lies the Weak Base Class problem
constructor(name) {
super(name, "penguin");
}
swim() {
console.log(`${this.name} is swimming, not flying.`);
}
Let's test your knowledge. Click the correct answer from the options.
What is a flaw in class-based inheritance?
Click the option that best answers the question.
- Composition over inheritance
- Weak base class problem
- Inheritance over composition
- None of the above
Inheriting Properties and Methods with Prototypical Inheritance
Objects are not merely isolated entities; they possess intricate relationships that shape their behavior. One such relationship is prototypal inheritance
, a mechanism that allows objects to inherit properties and methods from other objects, forming a hierarchical lineage.
Imagine a grand tapestry woven with threads of interconnectedness, each thread representing an object. This tapestry is the prototype chain
, where objects inherit traits from their ancestors, much like children inherit traits from their parents.
To illustrate this concept, let's consider the familiar example of a car and its relationship to the broader category of vehicles:
1const car = {
2 name: "BMW",
3 power: "2000 BHP",
4 color: "black"
5};
6
7console.log(car);
8console.log(car.name);
9console.log(car.power);
10console.log(car.color);
Examining the car object in the console reveals a link to the Object prototype, the foundation of all JavaScript objects. This connection grants the car access to the vast array of properties and methods inherent in the Object
prototype.

Now, let's introduce a new object, vehicle
, representing the generic concept of a vehicle:
1const vehicle = {
2 wheels: 4
3};
Here, we define a property called wheels
with a value of 4, establishing a default value for the number of wheels for any vehicle.
To connect the car to this broader vehicle category, we employ the Object.setPrototypeOf()
method:
1Object.setPrototypeOf(car, vehicle);
2
3console.log(car.wheels);
By assigning vehicle
as the prototype of car, we establish a hierarchical relationship, empowering the car to inherit properties from its vehicle ancestor. Printing the wheels
property of the car object now yields the value 4, demonstrating the successful inheritance of this trait.
This mechanism extends to methods as well, as methods in JavaScript are essentially properties of objects. Prototypical inheritance enables objects to inherit the functionality of their ancestors, promoting code reuse and maintainability.
Build your intuition. Fill in the missing part by typing it in.
The prototype of an object can be retrieved using the _ method
Write the missing line below.
What to use?
The main problem with class inheritance is that there is a side effect when inheriting a class from another. Every sub-class inherits properties and methods from the parent class. As mentioned above, this can quickly lead to huge problems if not used properly. This is where the age-old design pattern of composition over inheritance comes into the picture.
When people say prefer composition over inheritance, they are referring to class inheritance. There are a lot of flaws with class-based inheritance, but not all inheritance has these issues. Composition in JavaScript is implemented through concatenative inheritance-- wait, what is that?
Concatenative Inheritance
Prototypal inheritance, the cornerstone of JavaScript's object-oriented programming paradigm, is fundamentally rooted in concatenative inheritance
. This concept entails merging properties from one object into another, creating a new composite object. Unlike class inheritance, where parent-child relationships dictate the inheritance hierarchy, concatenative inheritance
offers greater flexibility and independence.
To illustrate this concept, consider the following code snippet:
1const wheels = { wheels: 4 };
2const color = { color: "black" };
3
4const Car = () => {
5 return Object.assign({}, wheels, color);
6};
7
8const car = Car();
9console.log(car);
In this example, we define two objects, wheels
and color
, encapsulating the essential characteristics of a car. The Car
function utilizes the Object.assign()
method to create a new object, car
, by combining the properties of wheels
and color
. This process exemplifies concatenative inheritance in action.
Now, let's envision a scenario where we want to create a black truck with enhanced power and wheels. We can leverage the existing color
object and introduce new properties, such as power
and wheels
, specifically tailored to the truck:
1const power = { power: "3000 BHP" };
2const truckWheels = { wheels: 6 };
3
4const Truck = () => {
5 return Object.assign({}, color, power, truckWheels);
6};
7
8const truck = Truck();
9console.log(truck);
The Truck
function employs the same concatenative approach, combining the color
object with the newly defined power
and truckWheels
objects. This demonstrates the flexibility of concatenative inheritance, allowing us to create new objects without inheriting any unwanted properties from previous creations.
Concatenative inheritance offers a distinct advantage over class inheritance, providing a more granular approach to object composition. It empowers developers to selectively inherit properties, tailoring objects to specific requirements. This flexibility is particularly valuable in JavaScript, where the lack of class-based inheritance constraints necessitates alternative strategies for object creation.
xxxxxxxxxx
let power = { power: "8000 BHP" }
const Truck = () => {
return Object.assign({}, power, color, wheels);
}
let blackTruck = Truck();
blackTruck["wheels"] = 6;
console.log(blackTruck);
Summing up
Prototypical inheritance is usually misunderstood with prototype delegation, but it is more related to concatenative inheritance. It simplifies object creation and sets clear boundaries about what is to be inherited and what is not. It has clear advantages over class inheritance and solves many design flaws of the same. We want to sign off with a famous problem of class inheritance, the Gorilla/Banana problem:
Imagine you simply desire a ripe banana to enjoy. However, when employing class inheritance, you inadvertently inherit not only the banana but also the gorilla holding it and, by extension, the entire jungle surrounding them. This excessive inheritance of unwanted properties can lead to bloated and unwieldy code structures.
To overcome this hurdle, concatenative inheritance provides a more controlled and flexible alternative. By selectively merging desired properties from various sources, concatenative inheritance allows you to obtain the banana without inheriting the entire jungle along with it.
In essence, concatenative inheritance empowers developers to craft lean, maintainable, and reusable code, avoiding the pitfalls of class inheritance and achieving greater precision in object composition.
Are you sure you're getting this? Click the correct answer from the options.
Prototypical inheritance is the same as prototype delegation. True or False?
Click the option that best answers the question.
- True
- False
One Pager Cheat Sheet
Objects in JavaScript are based on
prototypesrather than classes, so it is important to understand the distinction between class-based and prototypical inheritance.
- JavaScript utilizes a
prototype chain
instead of classical inheritance to make it more powerful than traditional Object Oriented Programming. - Objects can be created with the Object.create() method, a built-in JavaScript method that is part of the
prototype chain
, allowing for moreflexibility
andcontrol
over objects used for programming. - With the introduction of the
class
keyword in ES6, developers have been able to create class-based inheritance models; however, theWeak Base Class
problem arises when inheriting from a class which itself inherits from a superclass. - The Weak Base Class problem occurs when a subclass
C
inherits
from not only its intended parent classB
, but also from the parent class ofB
(the base class or superclassA
) implicitly, which can lead to unexpected and unpredictable behaviour requiring additional efforts for addressing. - Prototypical inheritance allows
Objects
toinherit
properties and methods from theirprototypes
, looking up the chain if needed. - Objects can use prototypes to inherit properties and methods from their parent object through a process known as
Prototypical inheritance
. - In software design, it is generally preferable to use
composition over inheritance
to achieve concatenative inheritance for better manageability and scalability. Concatenative inheritance allows objects to be created from existing objects without inheriting properties from pre-existing parent-child relationships.
- Prototypical inheritance simplifies object creation, has clear advantages over class inheritance, and solves the classic "Gorilla/Banana" problem.
- Prototypical Inheritance involves inheriting methods or features from a
prototype
, while Prototype Delegation is a built-in language feature of JavaScript in which an object delegates an attribute to another object in its prototype chain.