Prototypes & Inheritance
Prototype chain, prototypal inheritance, and Object.create
Understanding Prototypes
JavaScript uses prototypal inheritance, not classical inheritance like Java or C++. Every object in JavaScript has an internal link to another object called its prototype. When you access a property, JavaScript first looks on the object itself, then walks up the prototype chain.
Key Concepts
- [[Prototype]] — Internal link to the prototype object (accessed via
__proto__orObject.getPrototypeOf()) - prototype — Property on constructor functions that becomes [[Prototype]] of instances
- Prototype Chain — The chain of prototypes JavaScript searches for properties
The Prototype Chain
const animal = {
eat() {
console.log("Eating...");
}
};
const dog = {
bark() {
console.log("Woof!");
}
};
// Set dog's prototype to animal
Object.setPrototypeOf(dog, animal);
dog.bark(); // "Woof!" — found on dog
dog.eat(); // "Eating..." — found on dog's prototype (animal)
// The chain: dog → animal → Object.prototype → null
console.log(Object.getPrototypeOf(dog) === animal); // true
console.log(Object.getPrototypeOf(animal) === Object.prototype); // true
console.log(Object.getPrototypeOf(Object.prototype)); // null
Constructor Functions & prototype
Before ES6 classes, constructor functions were the primary way to create objects with shared behavior:
function Person(name, age) {
this.name = name;
this.age = age;
}
// Methods go on the prototype (shared across all instances)
Person.prototype.greet = function() {
return `Hi, I'm ${this.name}`;
};
Person.prototype.birthday = function() {
this.age++;
};
const alice = new Person("Alice", 30);
const bob = new Person("Bob", 25);
console.log(alice.greet()); // "Hi, I'm Alice"
console.log(bob.greet()); // "Hi, I'm Bob"
// Both share the same method
console.log(alice.greet === bob.greet); // true
// Check the prototype chain
console.log(alice.__proto__ === Person.prototype); // true
console.log(Person.prototype.constructor === Person); // true
Object.create()
Create objects with a specific prototype without using constructors:
const vehiclePrototype = {
start() {
console.log(`${this.type} starting...`);
},
stop() {
console.log(`${this.type} stopping...`);
}
};
// Create object with vehiclePrototype as its prototype
const car = Object.create(vehiclePrototype);
car.type = "Car";
car.wheels = 4;
car.start(); // "Car starting..."
// Create with properties defined
const motorcycle = Object.create(vehiclePrototype, {
type: { value: "Motorcycle", writable: true },
wheels: { value: 2, writable: true }
});
motorcycle.start(); // "Motorcycle starting..."
Inheritance with Constructor Functions
function Animal(name) {
this.name = name;
}
Animal.prototype.speak = function() {
console.log(`${this.name} makes a sound`);
};
function Dog(name, breed) {
// Call parent constructor
Animal.call(this, name);
this.breed = breed;
}
// Set up inheritance
Dog.prototype = Object.create(Animal.prototype);
Dog.prototype.constructor = Dog;
// Add Dog-specific methods
Dog.prototype.bark = function() {
console.log(`${this.name} barks!`);
};
// Override parent method
Dog.prototype.speak = function() {
console.log(`${this.name} says woof!`);
};
const rex = new Dog("Rex", "German Shepherd");
rex.speak(); // "Rex says woof!"
rex.bark(); // "Rex barks!"
console.log(rex instanceof Dog); // true
console.log(rex instanceof Animal); // true
ES6 Classes (Syntactic Sugar)
ES6 classes are syntactic sugar over prototype-based inheritance:
class Animal {
constructor(name) {
this.name = name;
}
speak() {
console.log(`${this.name} makes a sound`);
}
}
class Dog extends Animal {
constructor(name, breed) {
super(name); // Call parent constructor
this.breed = breed;
}
speak() {
console.log(`${this.name} barks!`);
}
fetch() {
console.log(`${this.name} fetches the ball`);
}
}
const buddy = new Dog("Buddy", "Labrador");
buddy.speak(); // "Buddy barks!"
// Under the hood, it's still prototypes
console.log(Dog.prototype.__proto__ === Animal.prototype); // true
Useful Prototype Methods
const obj = { a: 1 };
// Check prototype
Object.getPrototypeOf(obj); // Object.prototype
// Set prototype (avoid in production - slow)
Object.setPrototypeOf(obj, null);
// Check if property is own (not inherited)
obj.hasOwnProperty("a"); // true
// Check if object is in prototype chain
Animal.prototype.isPrototypeOf(buddy); // true
// Get own property names
Object.keys(obj); // ["a"] (enumerable)
Object.getOwnPropertyNames(obj); // ["a"] (all own props)
// Check instance
buddy instanceof Dog; // true
buddy instanceof Animal; // true
⚠️ Common Pitfalls
- Modifying built-in prototypes: Don't add methods to Array.prototype, Object.prototype, etc.
- Forgetting
new: Withoutnew, constructor functions don't create objects properly - Arrow functions: They don't have their own
prototypeproperty - Performance: Long prototype chains can slow down property lookups
💡 Key Takeaways
- • Every object has a [[Prototype]] linking to another object
- • Property lookups traverse the prototype chain until found or null
- • Constructor functions use
.prototypeto share methods - • Use
Object.create()for clean prototype-based inheritance - • ES6 classes are syntactic sugar over prototypes
- • Prefer composition over deep inheritance hierarchies