Classes and Prototypes

Understanding Classes and Prototypes in JavaScript

JavaScript is an object-oriented language, and as such, understanding the concepts of classes and prototypes is essential for writing clean and efficient code. These concepts are the backbone of object creation and inheritance in JavaScript. But how do classes and prototypes differ from each other, and how do they work together?

In this blog, we'll take a deep dive into these two fundamental concepts: classes and prototypes. We'll break them down in a way that’s easy to understand for beginners, with plenty of code examples to help solidify your understanding.

What Are Classes?

In simple terms, a class in JavaScript is like a blueprint for creating objects. It defines the properties and methods that an object created from the class will have. JavaScript classes were introduced in ES6 (ECMAScript 2015) and provide a more structured way of creating objects compared to the older function-based constructor methods.

Here's an example of a simple class:

class Car {
    constructor(make, model) {
        this.make = make;
        this.model = model;
    }
 
    displayInfo() {
        console.log(`Car make: ${this.make}, model: ${this.model}`);
    }
}
 
const myCar = new Car('Toyota', 'Corolla');
myCar.displayInfo();

Breakdown of the Code:

  • class Car: This creates a class named Car.
  • constructor(make, model): The constructor is a special method used to initialize an object when it is created. It's called automatically when you create a new instance of the class using the new keyword.
  • this.make and this.model: These are the properties of the object. this refers to the instance of the object being created.
  • displayInfo(): This is a method that belongs to the class, which can be called on any object created from the class.

In the above code, we created a Car class and an object myCar using the new keyword. The myCar object now has the properties make and model and can use the displayInfo method.

What Are Prototypes?

Before classes were introduced in JavaScript, inheritance and object construction were handled using prototypes. Every JavaScript object has an internal property called [[Prototype]], which is an object itself. This property points to the prototype of the object, and any properties or methods defined on the prototype are inherited by the object.

Let's look at an example of using prototypes:

function Car(make, model) {
    this.make = make;
    this.model = model;
}
 
Car.prototype.displayInfo = function() {
    console.log(`Car make: ${this.make}, model: ${this.model}`);
};
 
const myCar = new Car('Honda', 'Civic');
myCar.displayInfo();

Breakdown of the Code:

  • function Car(make, model): This is the function that acts as a constructor. It defines the properties make and model for the Car object.
  • Car.prototype.displayInfo: Here, we’re adding a method displayInfo to the Car function’s prototype, making it available to all instances of Car.
  • const myCar = new Car('Honda', 'Civic'): We create a new instance of the Car function. myCar inherits the displayInfo method from Car.prototype.

In the prototype-based approach, we directly define methods on the prototype of the function, and these methods are shared by all instances of the function. This is why prototype-based inheritance is sometimes called classical inheritance in JavaScript.

How Classes and Prototypes Relate

Classes in JavaScript are syntactic sugar over the older prototype-based inheritance. When you create an instance of a class, JavaScript automatically sets up a prototype chain behind the scenes. This chain connects the object to the class’s prototype, which allows the object to access the methods defined on the class.

Let’s look at an example where we mix both classes and prototypes:

class Car {
    constructor(make, model) {
        this.make = make;
        this.model = model;
    }
}
 
Car.prototype.displayInfo = function() {
    console.log(`Car make: ${this.make}, model: ${this.model}`);
};
 
const myCar = new Car('Ford', 'Mustang');
myCar.displayInfo();

Breakdown:

  • We created a Car class that defines make and model properties.
  • We then added the displayInfo method to the Car prototype, allowing all Car instances to access this method.

Even though we used the class syntax, the prototype-based behavior is still at work. In fact, behind the scenes, JavaScript has created a prototype for the Car class, and the method displayInfo is being inherited from it.

Prototypes and Inheritance

In JavaScript, inheritance is achieved through prototypes. When an object tries to access a property or method, JavaScript first checks if it exists on the object itself. If not, it checks the object's prototype, then the prototype's prototype, and so on, until it reaches null.

Let’s see how inheritance works with prototypes:

function Vehicle(make, model) {
    this.make = make;
    this.model = model;
}
 
Vehicle.prototype.displayInfo = function() {
    console.log(`Vehicle make: ${this.make}, model: ${this.model}`);
};
 
function Car(make, model, type) {
    Vehicle.call(this, make, model);  // Call the parent constructor
    this.type = type;
}
 
Car.prototype = Object.create(Vehicle.prototype);  // Inherit from Vehicle prototype
Car.prototype.constructor = Car;  // Set the constructor back to Car
 
Car.prototype.displayType = function() {
    console.log(`Car type: ${this.type}`);
};
 
const myCar = new Car('Tesla', 'Model S', 'Electric');
myCar.displayInfo();  // Inherited from Vehicle
myCar.displayType();  // Defined in Car

Breakdown:

  • Vehicle constructor: The parent constructor that defines make and model properties.
  • Car constructor: The child constructor that calls the Vehicle constructor using Vehicle.call(this, make, model) and adds the type property.
  • Object.create(Vehicle.prototype): This sets up the prototype chain so that Car inherits from Vehicle.
  • myCar.displayInfo(): Even though displayInfo is defined on the Vehicle prototype, myCar can still access it due to the prototype inheritance.

Classes vs Prototypes

To summarize, classes provide a more structured and readable way of defining objects and their methods. They are a higher-level abstraction over the traditional prototype-based inheritance. Prototypes, on the other hand, offer a more flexible way of handling inheritance, where you can add properties and methods dynamically.

FeatureClassesPrototypes
Syntaxclass syntax (ES6)Function-based
InheritanceBuilt-in prototype chainManual setup using .prototype
MethodsDefined inside the classDefined on the prototype object
Constructorconstructor methodConstructor functions
ExtensibilityLess flexibleMore flexible

Best Practices

  • Use classes for object creation when working with modern JavaScript (ES6+). They are cleaner, more readable, and easier to maintain.
  • Use prototypes for more advanced cases, such as adding methods dynamically or for performance optimization when working with many objects.

Practice Code Snippet for You

JavaScript Code Runner on: classes

IndGeek provides solutions in the software field, and is a hub for ultimate Tech Knowledge.