prototype-based (JavaScript)
Learn prototype-based (JavaScript) step by step with clear examples and exercises.
Title: Prototype-Based JavaScript Programming - A full guide
Why This Matters
Prototype-based programming is a fundamental concept in understanding JavaScript's unique approach to object-oriented programming. It sets JavaScript apart from other popular languages like Python and Java, and mastering it will help you write more efficient, flexible, and maintainable code. Understanding prototypes can also help you debug real-world issues that may arise in your projects.
JavaScript's prototype-based system allows for dynamic object creation, making it easier to add new properties and methods to objects at runtime. This flexibility is one of the key reasons behind JavaScript's popularity as a versatile language for web development.
Prerequisites
Before diving into prototype-based programming, you should have a good understanding of the following topics:
- JavaScript basics (variables, data types, operators, etc.)
- Functions and function scope
- Objects and properties
- The
thiskeyword - ES6 syntax (optional but recommended for modern JavaScript development)
Core Concept
In prototype-based programming, objects are created by cloning existing objects, known as prototypes. Every object in JavaScript has an internal property called [[Prototype]], which points to another object that serves as a blueprint for the current object. This process continues until we reach the top of the chain, which is the built-in object Object.
When you create a new object using the {} syntax or the new Object() constructor, JavaScript automatically sets its prototype to the Object object. However, when you create objects using a constructor function, you can specify the prototype manually:
function Car(make, model) {
this.make = make;
this.model = model;
}
Car.prototype.drive = function() {
console.log(`Driving ${this.make} ${this.model}`);
};
const myCar = new Car('Toyota', 'Corolla');
myCar.drive(); // Output: Driving Toyota Corolla
In this example, the Car function serves as a constructor, and its prototype (Car.prototype) is an object that contains the drive method. When we create a new Car instance using the new keyword, JavaScript automatically sets its prototype to Car.prototype.
Understanding the Prototype Chain
The prototype chain allows objects to inherit properties and methods from other objects. If a property or method is not found in an object, JavaScript will search for it in the object's prototype, and so on up the chain until it reaches the top (Object). This mechanism enables JavaScript to share common functionality across multiple objects without duplicating code.
const car = new Car('Toyota', 'Corolla');
console.log(car.hasOwnProperty('drive')); // Output: false
console.log(Car.prototype.hasOwnProperty('drive')); // Output: true
Prototype vs. Instance Properties
When you set a property directly on an object, it becomes an instance property. If that property exists in the prototype, it is said to be a prototype property. Accessing a property first checks if it exists as an instance property; if not, it looks for it in the prototype chain.
const car = new Car('Toyota', 'Corolla');
car.color = 'blue'; // Instance property
console.log(car.hasOwnProperty('color')); // Output: true
console.log(Car.prototype.hasOwnProperty('color')); // Output: false
Worked Example
Let's create a simple inheritance hierarchy for shapes:
function Shape() {}
Shape.prototype.area = function() {
throw new Error('Must be implemented by subclasses');
};
function Rectangle(width, height) {
this.width = width;
this.height = height;
}
Rectangle.prototype = Object.create(Shape.prototype);
Rectangle.prototype.constructor = Rectangle;
Rectangle.prototype.area = function() {
return this.width * this.height;
};
const square = new Rectangle(4, 4);
console.log(square.area()); // Output: 16
In this example, we first define a Shape constructor with an empty prototype. Then, we create a Rectangle constructor that inherits from the Shape prototype using Object.create(). We also override the area method on the Rectangle.prototype to compute the area of a rectangle.
Overriding Methods in Subclasses
If a subclass needs to modify the behavior of a method inherited from its parent class, it can do so by redefining the method in its prototype:
function Shape() {}
Shape.prototype.draw = function() {
console.log('Drawing shape');
};
function Rectangle(width, height) {
this.width = width;
this.height = height;
}
Rectangle.prototype = Object.create(Shape.prototype);
Rectangle.prototype.constructor = Rectangle;
// Override the draw method for Rectangles
Rectangle.prototype.draw = function() {
console.log('Drawing rectangle');
};
const rectangle = new Rectangle(4, 4);
rectangle.draw(); // Output: Drawing rectangle
Common Mistakes
- ### Forgetting to set the constructor property on the prototype:
// Incorrect code
function Car(make, model) {
this.make = make;
this.model = model;
}
Car.prototype.drive = function() {
console.log(`Driving ${this.make} ${this.model}`);
};
const car1 = new Car('Toyota', 'Corolla');
car1.drive(); // Output: Driving Toyota Corolla
Car.prototype.drive = function() {
console.log(`Driving a different car now`);
};
const car2 = new Car('Honda', 'Civic');
car2.drive(); // Output: Driving a different car now
To fix this, set the constructor property on the prototype:
// Corrected code
function Car(make, model) {
this.make = make;
this.model = model;
}
Car.prototype.constructor = Car;
Car.prototype.drive = function() {
console.log(`Driving ${this.make} ${this.model}`);
};
const car1 = new Car('Toyota', 'Corolla');
car1.drive(); // Output: Driving Toyota Corolla
Car.prototype.driveFast = function() {
console.log(`Driving fast now`);
};
car1.driveFast(); // Output: Driving fast now
- ### Modifying the prototype directly:
// Incorrect code
function Car(make, model) {
this.make = make;
this.model = model;
}
Car.prototype.drive = function() {
console.log(`Driving ${this.make} ${this.model}`);
};
const car1 = new Car('Toyota', 'Corolla');
car1.drive(); // Output: Driving Toyota Corolla
Car.prototype.drive = function() {
console.log(`Driving a different car now`);
};
const car2 = new Car('Honda', 'Civic');
car2.drive(); // Output: Driving a different car now
Modifying the prototype directly will affect all instances of that constructor, which can lead to unexpected behavior. Instead, create methods on each instance or use class-based inheritance (ES6 classes) if you need more control over the prototype chain:
// Corrected code using a method on each instance
function Car(make, model) {
this.make = make;
this.model = model;
}
Car.prototype.drive = function() {
console.log(`Driving ${this.make} ${this.model}`);
};
const car1 = new Car('Toyota', 'Corolla');
car1.drive(); // Output: Driving Toyota Corolla
Car.prototype.driveFast = function() {
console.log(`Driving fast now`);
};
car1.driveFast(); // Output: Driving fast now
Practice Questions
- Create a constructor
Animalwith propertiesname,legs, andsound. Add a methodmakeSound()to the prototype that logs the animal's sound (e.g., "The cat makes a meow sound"). Create an instance of theDogsubclass, which should inherit fromAnimaland override themakeSound()method to log "Woof!".
- Implement a simple inheritance hierarchy for shapes:
Shape,Rectangle,Square, andCircle. Each shape should have an area method that computes its area based on the dimensions provided. Create instances of each shape and compute their areas.
FAQ
### What is the difference between prototype-based programming and class-based inheritance?
Prototype-based programming relies on objects cloning other objects to create new ones, while class-based inheritance involves creating a blueprint (class) that defines properties and methods shared by all instances. JavaScript supports both approaches, with prototype-based programming being the default mechanism for object creation.
### How can I access the prototype of an object in JavaScript?
To access the prototype of an object, you can use the Object.getPrototypeOf() method:
const myCar = new Car('Toyota', 'Corolla');
console.log(Object.getPrototypeOf(myCar) === Car.prototype); // Output: true
### Can I modify the prototype chain directly in JavaScript?
Yes, you can modify the prototype chain by changing the [[Prototype]] property of an object or by setting the prototype of a constructor function to another object. However, doing so may lead to unexpected behavior and should be used with caution. It's generally recommended to use inheritance patterns like those shown in this lesson to create new objects from existing ones.
### How can I extend an existing object with additional methods or properties?
To add new methods or properties to an object, you can use the Object.assign() method:
const car = { make: 'Toyota', model: 'Corolla' };
const extensions = { color: 'blue', driveFast: function() { console.log('Driving fast!'); } };
// Extend the car object with new properties and methods
Object.assign(car, extensions);
console.log(car); // Output: { make: 'Toyota', model: 'Corolla', color: 'blue', driveFast: [Function] }
### What is the purpose of the hasOwnProperty() method in JavaScript?
The hasOwnProperty() method checks whether an object has a property directly (i.e., not inherited from its prototype). It can be useful when you want to check if an object has a specific instance property:
const car = { make: 'Toyota', model: 'Corolla' };
console.log(car.hasOwnProperty('make')); // Output: true
console.log(car.hasOwnProperty('toString')); // Output: false (inherited from Object prototype)