Prototypal Inheritance in JavaScript
Unlike classical inheritance found in languages like Java or C++, JavaScript uses prototypal inheritance. In this system, objects inherit directly from other objects rather than from classes.
The Prototype Chain
Every JavaScript object has an internal and hidden property known as [[Prototype]] (historically accessed via __proto__). When you try to access a property or method on an object, JavaScript will first look at the object itself. If it doesn't find it there, it looks at the object's prototype, then the prototype's prototype, continuing up the prototype chain until it reaches an object whose prototype is null (usually Object.prototype).
Example of the Prototype Chain
const animal = {
eats: true,
walk() {
console.log("Animal walk");
}
};
const rabbit = {
jumps: true,
__proto__: animal // Sets animal as the prototype of rabbit
};
console.log(rabbit.eats); // true (inherited from animal)
console.log(rabbit.jumps); // true (found on rabbit itself)
rabbit.walk(); // "Animal walk" (inherited from animal)
Object.create()
The modern and recommended way to create an object with a specific prototype is using Object.create(). It avoids the performance issues and legacy nature of __proto__.
const car = {
wheels: 4,
drive() {
console.log("Driving...");
}
};
const electricCar = Object.create(car);
electricCar.battery = "100kWh";
console.log(electricCar.wheels); // 4
electricCar.drive(); // "Driving..."
Object.getPrototypeOf() and Object.setPrototypeOf()
To read or set the prototype of an object after its creation, use Object.getPrototypeOf and Object.setPrototypeOf. Note that changing a prototype dynamically using setPrototypeOf can be a very slow operation in JavaScript engines and should generally be avoided if performance is a concern.
const base = { a: 1 };
const child = Object.create(base);
console.log(Object.getPrototypeOf(child) === base); // true
const newBase = { b: 2 };
Object.setPrototypeOf(child, newBase);
console.log(child.b); // 2
console.log(child.a); // undefined
Constructor Functions and the prototype Property
Before ES6 classes, constructor functions were the primary way to create object instances sharing the same prototype.
function Person(name) {
this.name = name;
}
// Adding a method to the prototype so all instances share it
Person.prototype.sayHello = function() {
console.log(`Hello, my name is ${this.name}`);
};
const alice = new Person("Alice");
const bob = new Person("Bob");
alice.sayHello(); // "Hello, my name is Alice"
When new Person() is called, JavaScript creates a new object and sets its internal [[Prototype]] to Person.prototype.
ES6 Classes: Syntactic Sugar
ES6 introduced the class keyword, which provides a cleaner, classical-style syntax. However, under the hood, it is still doing prototypal inheritance.
class Animal {
constructor(name) {
this.name = name;
}
speak() {
console.log(`${this.name} makes a noise.`);
}
}
class Dog extends Animal {
speak() {
console.log(`${this.name} barks.`);
}
}
const d = new Dog("Mitzie");
d.speak(); // Mitzie barks.
In this example, Dog.prototype inherits from Animal.prototype.
Summary
- JavaScript objects have an internal link to another object called their prototype.
- The prototype chain is traversed when resolving property lookups.
- Use
Object.create(proto)to safely create objects with a specific prototype. - ES6
classandextendsare syntactic sugar over prototypal inheritance.