Prototypal Inheritance Explained
Answer
JavaScript uses prototypal inheritance, where objects inherit directly from other objects. Every object has an internal link to another object called its prototype.
Prototype Chain
How It Works
// When accessing a property:
const person = {
name: "John",
greet() {
return `Hello, I'm ${this.name}`;
},
};
const employee = Object.create(person);
employee.role = "Developer";
console.log(employee.role); // "Developer" (own property)
console.log(employee.name); // "John" (from prototype)
console.log(employee.greet()); // "Hello, I'm John"
// Check where property comes from
console.log(employee.hasOwnProperty("role")); // true
console.log(employee.hasOwnProperty("name")); // false
Constructor Functions
// Constructor function (pre-ES6 way)
function Person(name, age) {
this.name = name;
this.age = age;
}
// Methods on prototype (shared by all instances)
Person.prototype.greet = function () {
return `Hi, I'm ${this.name}`;
};
Person.prototype.isAdult = function () {
return this.age >= 18;
};
const john = new Person("John", 30);
const jane = new Person("Jane", 25);
console.log(john.greet()); // "Hi, I'm John"
console.log(jane.greet()); // "Hi, I'm Jane"
// Both share the same greet function
console.log(john.greet === jane.greet); // true
ES6 Classes (Syntactic Sugar)
// Classes are just syntactic sugar over prototypes
class Person {
constructor(name, age) {
this.name = name;
this.age = age;
}
greet() {
return `Hi, I'm ${this.name}`;
}
static createAnonymous() {
return new Person("Anonymous", 0);
}
}
class Employee extends Person {
constructor(name, age, role) {
super(name, age);
this.role = role;
}
greet() {
return `${super.greet()}, I work as a ${this.role}`;
}
}
const dev = new Employee("John", 30, "Developer");
console.log(dev.greet()); // "Hi, I'm John, I work as a Developer"
Prototype vs proto vs prototype
function Person(name) {
this.name = name;
}
const john = new Person("John");
// prototype: Property of constructor functions
console.log(Person.prototype); // { constructor: Person }
// __proto__: Reference to object's prototype (deprecated, use Object.getPrototypeOf)
console.log(john.__proto__ === Person.prototype); // true
console.log(Object.getPrototypeOf(john) === Person.prototype); // true
// All instances share the same prototype
const jane = new Person("Jane");
console.log(john.__proto__ === jane.__proto__); // true
Property Lookup Chain
const animal = {
eats: true,
walk() {
return "Walking...";
},
};
const rabbit = Object.create(animal);
rabbit.jumps = true;
rabbit.walk = function () {
return "Hopping!"; // Overrides parent
};
console.log(rabbit.jumps); // true (own property)
console.log(rabbit.eats); // true (inherited)
console.log(rabbit.walk()); // "Hopping!" (own method shadows parent)
Object.create() Patterns
// Create object with specific prototype
const personMethods = {
greet() {
return `Hello, ${this.name}`;
},
introduce() {
return `I am ${this.name}, ${this.age} years old`;
},
};
function createPerson(name, age) {
const person = Object.create(personMethods);
person.name = name;
person.age = age;
return person;
}
const john = createPerson("John", 30);
console.log(john.greet()); // "Hello, John"
Checking Prototypes
const arr = [1, 2, 3];
// instanceof checks prototype chain
console.log(arr instanceof Array); // true
console.log(arr instanceof Object); // true
// isPrototypeOf checks if object is in chain
console.log(Array.prototype.isPrototypeOf(arr)); // true
console.log(Object.prototype.isPrototypeOf(arr)); // true
// Get and set prototype
console.log(Object.getPrototypeOf(arr) === Array.prototype); // true
// Set prototype (not recommended for performance)
const proto = { greet: () => "Hi" };
const obj = {};
Object.setPrototypeOf(obj, proto);
Comparison with Classical Inheritance
| Classical (Java/C++) | Prototypal (JavaScript) |
|---|---|
| Classes define blueprints | Objects inherit from objects |
| Copy of class creates object | Objects link to prototype |
| Rigid hierarchy | Flexible, dynamic |
| Methods copied to each instance | Methods shared on prototype |
Performance Note
// ✅ Methods on prototype (shared, memory efficient)
function Person(name) {
this.name = name;
}
Person.prototype.greet = function () {
return `Hi, ${this.name}`;
};
// ❌ Methods in constructor (copied to each instance)
function Person(name) {
this.name = name;
this.greet = function () {
// New function per instance!
return `Hi, ${this.name}`;
};
}
Key Points
- Objects inherit directly from other objects
- Prototype chain is followed until null
- Properties on object shadow prototype properties
- Methods should be on prototype for efficiency
- ES6 classes are syntactic sugar over prototypes
- Use
Object.getPrototypeOf()instead of__proto__