Difficulty: Intermediate
Explain prototypal inheritance in JavaScript. What is the prototype chain?
JavaScript uses prototypal inheritance, where objects can inherit properties and methods from other objects. Every object has an internal [[Prototype]] link (accessible via __proto__ or Object.getPrototypeOf()) that points to another object.
When you access a property on an object, JavaScript first looks at the object itself. If not found, it follows the prototype chain - checking the object's prototype, then the prototype's prototype, and so on until it reaches null (the end of the chain).
Functions have a 'prototype' property (not to be confused with __proto__). When you use 'new' to create an instance, the instance's __proto__ is set to the constructor's prototype. This is how methods are shared across all instances without duplicating them in memory.
Object.create() lets you create objects with a specific prototype, enabling clean prototypal inheritance without constructors.
const animal = {
type: 'Animal',
speak() {
return `${this.name} says ${this.sound}`;
}
};
const dog = Object.create(animal);
dog.name = 'Rex';
dog.sound = 'Woof';
console.log(dog.speak()); // Uses inherited method
console.log(dog.type); // Inherited property
console.log(dog.hasOwnProperty('name')); // true (own)
console.log(dog.hasOwnProperty('type')); // false (inherited)
// Walking the prototype chain
console.log(Object.getPrototypeOf(dog) === animal); // true
console.log(Object.getPrototypeOf(animal) === Object.prototype); // true
console.log(Object.getPrototypeOf(Object.prototype)); // null (end)
dog inherits from animal, which inherits from Object.prototype. Property lookup walks up this chain until it finds the property or reaches null.
function Person(name, age) {
this.name = name;
this.age = age;
}
// Methods on prototype are shared across all instances
Person.prototype.greet = function() {
return `Hi, I'm ${this.name}, age ${this.age}`;
};
const alice = new Person('Alice', 25);
const bob = new Person('Bob', 30);
console.log(alice.greet());
console.log(bob.greet());
// Both share the same method reference
console.log(alice.greet === bob.greet); // true
// Instance vs prototype relationship
console.log(alice.__proto__ === Person.prototype); // true
console.log(alice instanceof Person); // true
console.log(alice.constructor === Person); // true
Methods defined on the prototype are shared (one copy in memory). Each instance's __proto__ points to Person.prototype.
function Shape(color) {
this.color = color;
}
Shape.prototype.describe = function() {
return `A ${this.color} ${this.type}`;
};
function Circle(color, radius) {
Shape.call(this, color); // Call parent constructor
this.type = 'circle';
this.radius = radius;
}
// Set up inheritance chain
Circle.prototype = Object.create(Shape.prototype);
Circle.prototype.constructor = Circle;
Circle.prototype.area = function() {
return Math.PI * this.radius 2;
};
const c = new Circle('red', 5);
console.log(c.describe()); // inherited from Shape
console.log(c.area().toFixed(2)); // own method
console.log(c instanceof Circle); // true
console.log(c instanceof Shape); // true
Shape.call(this, color) borrows the parent constructor. Object.create(Shape.prototype) sets up the chain so Circle instances can access Shape methods.
Prototype, Prototype Chain, __proto__, Object.create, Inheritance, Constructor