Prototypal Inheritance
JavaScript uses prototypal inheritance.
That means objects can inherit directly from other objects.
Classes in JavaScript are built on top of this prototype system.
To understand classes deeply, you need to understand prototypes.
Objects Inherit From Objects
const animal = {
speak() {
return "Animal sound";
},
};
const dog = Object.create(animal);
dog.name = "Bruno";
console.log(dog.speak()); // Animal sounddog inherits from animal.
When JavaScript cannot find speak on dog, it looks at animal.
Constructor Functions
Before classes, JavaScript commonly used constructor functions.
function User(name) {
this.name = name;
}
const user = new User("Alice");
console.log(user.name); // AliceWhen called with new, a constructor function creates a new object.
Inside the function, this refers to the new object.
The prototype Property
Every normal function has a prototype property.
function User(name) {
this.name = name;
}
console.log(User.prototype);Objects created with new User() inherit from User.prototype.
const user = new User("Alice");
console.log(Object.getPrototypeOf(user) === User.prototype); // trueThis is the key connection:
new User() creates an object whose prototype is User.prototype.Adding Shared Methods
You can add methods to the constructor's prototype.
function User(name) {
this.name = name;
}
User.prototype.greet = function () {
return `Hello, ${this.name}`;
};
const alice = new User("Alice");
const bob = new User("Bob");
console.log(alice.greet()); // Hello, Alice
console.log(bob.greet()); // Hello, Bob
console.log(alice.greet === bob.greet); // trueBoth instances share the same greet function.
Each instance has its own name.
Why Put Methods on the Prototype?
This is less efficient:
function User(name) {
this.name = name;
this.greet = function () {
return `Hello, ${this.name}`;
};
}Every instance gets its own copy of greet.
This is more efficient:
function User(name) {
this.name = name;
}
User.prototype.greet = function () {
return `Hello, ${this.name}`;
};All instances share one method.
The constructor Property
By default, a function's prototype has a constructor property.
function User(name) {
this.name = name;
}
const user = new User("Alice");
console.log(user.constructor === User); // trueThe constructor property points back to the function.
Do not rely on it too heavily for application logic.
It is useful to recognize when inspecting objects.
Class Syntax Does the Same Thing
This class:
class User {
constructor(name) {
this.name = name;
}
greet() {
return `Hello, ${this.name}`;
}
}is similar in concept to:
function User(name) {
this.name = name;
}
User.prototype.greet = function () {
return `Hello, ${this.name}`;
};Class methods are placed on the class prototype.
const user = new User("Alice");
console.log(Object.getPrototypeOf(user) === User.prototype); // trueClasses are cleaner syntax over prototypes.
Inheritance With Constructor Functions
Before class extends, inheritance was more manual.
function Animal(name) {
this.name = name;
}
Animal.prototype.speak = function () {
return `${this.name} makes a sound`;
};
function Dog(name, breed) {
Animal.call(this, name);
this.breed = breed;
}
Dog.prototype = Object.create(Animal.prototype);
Dog.prototype.constructor = Dog;
Dog.prototype.bark = function () {
return `${this.name} barks`;
};
const dog = new Dog("Bruno", "Labrador");
console.log(dog.speak()); // Bruno makes a sound
console.log(dog.bark()); // Bruno barksThis is verbose.
Modern classes make it easier:
class Animal {
constructor(name) {
this.name = name;
}
speak() {
return `${this.name} makes a sound`;
}
}
class Dog extends Animal {
constructor(name, breed) {
super(name);
this.breed = breed;
}
bark() {
return `${this.name} barks`;
}
}The class version still uses prototypes underneath.
Own Properties vs Prototype Methods
class User {
constructor(name) {
this.name = name;
}
greet() {
return `Hello, ${this.name}`;
}
}
const user = new User("Alice");
console.log(Object.hasOwn(user, "name")); // true
console.log(Object.hasOwn(user, "greet")); // falsename is an own property.
greet is inherited from User.prototype.
Best Practices
Use class syntax for most modern object-oriented JavaScript.
Understand prototypes because classes use them underneath.
Put shared methods on prototypes or class bodies, not inside every constructor call.
Avoid manually changing prototype chains unless you have a clear reason.
Use Object.create() when you intentionally need direct prototypal inheritance.
Common Mistakes
Mistake 1: Thinking Class Methods Are Own Properties
const user = new User("Alice");
Object.hasOwn(user, "greet"); // falseClass methods are usually on the prototype.
Mistake 2: Forgetting new With Constructor Functions
function User(name) {
this.name = name;
}
const user = User("Alice"); // wrongWithout new, this is not the new instance.
Classes prevent this mistake by throwing if called without new.
Mistake 3: Replacing a Prototype and Losing constructor
Dog.prototype = Object.create(Animal.prototype);This creates inheritance, but it also replaces the default prototype object.
Often old-style code then restores:
Dog.prototype.constructor = Dog;Modern class syntax avoids this manual work.
Quick Check
What does this log?
function User(name) {
this.name = name;
}
User.prototype.greet = function () {
return `Hello, ${this.name}`;
};
const alice = new User("Alice");
const bob = new User("Bob");
console.log(alice.greet === bob.greet);It logs:
trueBoth instances share the same prototype method.
Summary
Prototypal inheritance means objects can inherit from other objects.
- Constructor functions use
.prototypefor shared methods. - Objects created with
newinherit from the constructor's.prototype. - Prototype methods are shared between instances.
- Instance data is usually stored as own properties.
- Classes are modern syntax over the prototype system.
extendsis cleaner than manual prototype-chain setup.- Understanding prototypes helps explain how classes really work.