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
andmodel
for theCar
object. - Car.prototype.displayInfo: Here, we’re adding a method
displayInfo
to theCar
function’s prototype, making it available to all instances ofCar
. - const myCar = new Car('Honda', 'Civic'): We create a new instance of the
Car
function.myCar
inherits thedisplayInfo
method 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
Car
class that definesmake
andmodel
properties. - We then added the
displayInfo
method to theCar
prototype, allowing allCar
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
andmodel
properties. - Car constructor: The child constructor that calls the
Vehicle
constructor usingVehicle.call(this, make, model)
and adds thetype
property. - Object.create(Vehicle.prototype): This sets up the prototype chain so that
Car
inherits fromVehicle
. - myCar.displayInfo(): Even though
displayInfo
is defined on theVehicle
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.
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.