JavaScript Prototypes
Learn JavaScript Prototypes step by step with clear examples and exercises.
Why This Matters
In this comprehensive lesson, we will delve into the world of JavaScript prototypes - a fundamental concept that underpins object-oriented programming (OOP) in JavaScript. Understanding prototypes is crucial for acing interviews, debugging real-world issues, and writing efficient code. Let's embark on our journey!
Prerequisites
Before diving into the core concept of JavaScript prototypes, it's essential to have a solid grasp of the following topics:
- Basic JavaScript syntax and data types
- Functions in JavaScript
- Objects in JavaScript
- Understanding ES6 class syntax (optional but recommended)
- Familiarity with the concept of inheritance in object-oriented programming
Core Concept
What are Prototypes?
In JavaScript, every object has an internal property called [[Prototype]], which is a reference to another object. This other object serves as the prototype for the given object and provides default properties and methods that can be inherited. The prototype chain allows objects to inherit properties and methods from their prototypes, all the way up to the top of the chain, which is the Object constructor.
Creating and Accessing Prototypes
You can create a new object with a specified prototype by using the Object.create() method or by assigning the prototype directly:
let vehicle = {
// properties and methods go here
};
let car = Object.create(vehicle); // Using Object.create()
car.__proto__ = vehicle; // Assigning prototype directly
Inheritance and Method Overriding
When you access a property or method on an object that doesn't have it, JavaScript searches the prototype chain for the property or method. If it finds one, it returns that; otherwise, it returns undefined. This allows objects to inherit properties and methods from their prototypes.
You can also override properties and methods in child objects by defining them again:
let vehicle = {
startEngine() {
console.log('Engine started');
}
};
let car = Object.create(vehicle);
Object.defineProperty(car, 'startEngine', {
value: function() {
console.log(`Starting engine of ${this.make}`);
}
});
Now, when you call car.startEngine(), it will execute the method defined in the car object, and not the one inherited from the vehicle prototype.
Prototype Chains
The prototype chain is a series of objects that an object inherits properties and methods from. Each object's prototype is another object, which may have its own prototype, and so on, until we reach the top of the chain - the Object constructor. When you access a property or method on an object, JavaScript looks for it in the object itself, then moves up the prototype chain until it finds the property or method or reaches the end of the chain.
The Role of Constructors in Prototypes
In JavaScript, constructors are special functions that create new objects and set their prototypes to the constructor's prototype property. This allows multiple instances of an object to share the same prototype and its properties and methods:
function Vehicle(make, model) {
this.make = make;
this.model = model;
}
Vehicle.prototype.startEngine = function() {
console.log('Starting engine...');
};
let car = new Vehicle('Toyota', 'Corolla');
In this example, car is an instance of the Vehicle constructor and inherits properties and methods from its prototype (Vehicle.prototype).
Worked Example
Let's create a simple example of inheritance using prototypes:
function Vehicle(make, model) {
this.make = make;
this.model = model;
}
Vehicle.prototype.startEngine = function() {
console.log('Starting engine...');
};
let carPrototype = Object.create(Vehicle.prototype);
carPrototype.drive = function() {
this.startEngine();
console.log('Driving...');
};
let toyotaCorolla = Object.create(carPrototype);
toyotaCorolla.make = 'Toyota';
toyotaCorolla.model = 'Corolla';
toyotaCorolla.drive(); // Starts, drives, and stops the engine of a Toyota Corolla
Common Mistakes
- Forgetting to set the prototype: If you don't explicitly set an object's prototype, it will inherit from
Object.prototype. This can lead to unexpected behavior when accessing or defining properties and methods. - Overriding essential methods: Be careful when overriding methods like
toString()orhasOwnProperty(), as they have specific behaviors in JavaScript that may be relied upon by other parts of your code or built-in functions. - Misunderstanding prototype chains: It's important to understand how the prototype chain works and how it affects property and method lookup. A common mistake is assuming that properties and methods are always inherited from the immediate prototype, rather than searching the entire chain.
- Not using constructors properly: Constructors should be used to create new objects with a specific prototype and shared properties and methods. Failing to use constructors can lead to redundant code and inconsistent object structures.
- Confusing
__proto__with constructor's prototype: While you can access an object's prototype using the__proto__property, it's important to remember that this is not the same as a constructor's prototype property. The former refers to an individual object's prototype, while the latter is used when creating new objects with a constructor.
Practice Questions
- Create a
Personconstructor with properties for name, age, and occupation. Then create an instance ofStudent, which inherits fromPersonand has a property for school. - Write a function that takes an object and returns the length of its prototype chain (i.e., the number of prototypes between the given object and
Object.prototype). - Given the following objects, determine the output of each expression:
let vehicle = {
startEngine() {
console.log('Starting engine...');
}
};
let car = Object.create(vehicle);
car.make = 'Toyota';
car.model = 'Corolla';
let toyotaCorolla = Object.create(car);
toyotaCorolla.drive = function() {
this.startEngine();
console.log('Driving...');
};
console.log(toyotaCorolla.make); // Output: 'Toyota'
console.log(toyotaCorolla.hasOwnProperty('make')); // Output: true
console.log(vehicle.isPrototypeOf(toyotaCorolla)); // Output: true
- Implement a simple inheritance hierarchy for animals, where
Mammalhas propertiesnameandspecies, andCatinherits fromMammal. Create instances of bothMammalandCat, and demonstrate the use of prototypes to share common properties and methods. - Write a function that creates a new object with a specified prototype and properties, using either
Object.create()or by assigning the prototype directly.
FAQ
- Why don't we use classes in JavaScript for inheritance?
- While ES6 introduced classes, they are syntactic sugar that compile to prototype-based code. Under the hood, classes still rely on prototypes and the prototype chain.
- What happens when you call a method on an object that doesn't exist in its prototype chain?
- If you call a method on an object that doesn't exist in its prototype chain, JavaScript will return
undefined.
- Can I change an object's prototype after it has been created?
- Yes, you can change an object's prototype at any time by setting the
__proto__property or usingObject.setPrototypeOf(). However, changing an object's prototype doesn't affect objects that have already been created with that object as their prototype.
- What is the difference between
__proto__and constructor's prototype?
- The
__proto__property refers to an individual object's prototype, while the constructor's prototype property is used when creating new objects with a constructor.
- Why should I use constructors in JavaScript?
- Constructors help create new objects with a specific prototype and shared properties and methods. They also ensure that all instances of an object are created consistently.