Mark As Completed Discussion

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.

Object Oriented Programming vs JavaScript

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:

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

JAVASCRIPT
OUTPUT
:001 > Cmd/Ctrl-Enter to run, Cmd/Ctrl-/ to comment

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:

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

Inheriting Properties and Methods with Prototypical Inheritance

Now, let's introduce a new object, vehicle, representing the generic concept of a vehicle:

JAVASCRIPT
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:

JAVASCRIPT
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:

JAVASCRIPT
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:

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

JAVASCRIPT
OUTPUT
:001 > Cmd/Ctrl-Enter to run, Cmd/Ctrl-/ to comment

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 onprototypesrather 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 more flexibility and control 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, the Weak 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 class B, but also from the parent class of B (the base class or superclass A) implicitly, which can lead to unexpected and unpredictable behaviour requiring additional efforts for addressing.
  • Prototypical inheritance allows Objects to inherit properties and methods from their prototypes, 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.