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
newkeyword. - this.make and this.model: These are the properties of the object.
thisrefers 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
makeandmodelfor theCarobject. - Car.prototype.displayInfo: Here, we’re adding a method
displayInfoto theCarfunction’s prototype, making it available to all instances ofCar. - const myCar = new Car('Honda', 'Civic'): We create a new instance of the
Carfunction.myCarinherits thedisplayInfomethod fromCar.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
Carclass that definesmakeandmodelproperties. - We then added the
displayInfomethod to theCarprototype, allowing allCarinstances 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 CarBreakdown:
- Vehicle constructor: The parent constructor that defines
makeandmodelproperties. - Car constructor: The child constructor that calls the
Vehicleconstructor usingVehicle.call(this, make, model)and adds thetypeproperty. - Object.create(Vehicle.prototype): This sets up the prototype chain so that
Carinherits fromVehicle. - myCar.displayInfo(): Even though
displayInfois defined on theVehicleprototype,myCarcan 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.
| Feature | Classes | Prototypes |
|---|---|---|
| Syntax | class syntax (ES6) | Function-based |
| Inheritance | Built-in prototype chain | Manual setup using .prototype |
| Methods | Defined inside the class | Defined on the prototype object |
| Constructor | constructor method | Constructor functions |
| Extensibility | Less flexible | More 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.
