This content originally appeared on DEV Community and was authored by Dehemi Fabio
JavaScript is unique among programming languages because it uses prototypal inheritance instead of traditional class-based inheritance. This fundamental difference can be confusing at first, but once you understand how prototypes work, you'll have a much deeper grasp of JavaScript's powerful object system.
In this comprehensive guide, we'll explore how prototypes, constructors, and inheritance work together to create JavaScript's flexible object model.
Table of Contents
- The Foundation: JavaScript Objects
- Understanding Prototypes
- Constructor Functions
- Prototypal Inheritance
- Object.create() Method
- Property Lookup and Shadowing
- Inspecting Objects and Prototypes
- Modern ES6 Classes
- Best Practices and Quick Reference
The Foundation: JavaScript Objects
Before diving into prototypes, let's understand the basics of JavaScript objects. The most common way to create objects is with object literals:
let person = {
name: "John",
age: 30,
greet() {
return `Hello, I'm ${this.name}`;
}
};
console.log(person.name); // "John"
console.log(person.greet()); // "Hello, I'm John"
Important: let person = {}
is actually shorthand for let person = new Object()
. Both create objects that inherit from Object.prototype
, giving them access to built-in methods.
Understanding Prototypes
Every object in JavaScript has a hidden internal property called [[Prototype]]
. This creates a link to another object, allowing your object to "inherit" properties and methods.
Accessing an Object's Prototype
let person = {}; // Creates an empty object
// Recommended way to access prototype
console.log(Object.getPrototypeOf(person)); // Shows built-in methods
// Non-standard way (avoid in production code)
console.log(person.__proto__); // Same result, but deprecated
Even though person
appears empty, it has access to inherited methods:
console.log(person.toString()); // "[object Object]"
console.log(person.valueOf()); // {}
Where did these methods come from? They're inherited from Object.prototype
through the prototype chain!
The Prototype Chain
Different types of objects have different prototypes, creating inheritance chains:
let myArray = [1, 2, 3];
console.log(Object.getPrototypeOf(myArray)); // Shows Array methods like push(), pop()
let myString = "Hello";
console.log(Object.getPrototypeOf(myString)); // Shows String methods like charAt(), slice()
// For debugging/learning, you might see __proto__ used:
// console.log(myArray.__proto__); // Same result, but avoid in production
This creates prototype chains:
-
myArray
→Array.prototype
→Object.prototype
→null
-
myString
→String.prototype
→Object.prototype
→null
Constructor Functions
Constructor functions are templates for creating multiple similar objects. They become special when used with the new
keyword.
What the new
Keyword Does
Understanding what happens when you use new
is crucial:
// When you do: const car = new Car("Ford", "Mustang")
// JavaScript essentially does this:
function newOperator(constructor, ...args) {
// 1. Create empty object linked to constructor's prototype
const obj = Object.create(constructor.prototype);
// 2. Execute constructor with "this" bound to new object
const result = constructor.apply(obj, args);
// 3. Return the object (unless constructor returns something else)
return result instanceof Object ? result : obj;
}
Creating Your First Constructor
// Constructor function (note the capital letter)
function Car(make, model, year, color) {
// "this" refers to the newly created object
this.make = make;
this.model = model;
this.year = year;
this.color = color;
}
// Creating instances
const car1 = new Car("Ford", "Mustang", 2024, "red");
const car2 = new Car("Chevrolet", "Camaro", 2025, "blue");
console.log(car1.make); // "Ford"
console.log(car2.make); // "Chevrolet"
Adding Methods to Prototypes
For efficiency, add methods to the prototype rather than in the constructor:
// Add methods to the prototype (shared by all instances)
Car.prototype.drive = function() {
return `You drive the ${this.model}`;
};
Car.prototype.getInfo = function() {
return `${this.year} ${this.make} ${this.model} (${this.color})`;
};
console.log(car1.drive()); // "You drive the Mustang"
console.log(car2.getInfo()); // "2025 Chevrolet Camaro (blue)"
Instance vs Prototype Properties
Understanding the difference is key to efficient JavaScript:
// Instance properties (unique to each object)
console.log(car1.make); // "Ford" - stored directly on car1
console.log(car2.make); // "Chevrolet" - stored directly on car2
// Prototype methods (shared by all instances)
console.log(car1.drive === car2.drive); // true - same function reference
The prototype
vs __proto__
Relationship
This relationship often confuses beginners:
function Dog(name) {
this.name = name;
}
Dog.prototype.bark = function() {
return `${this.name} says woof!`;
};
const myDog = new Dog("Buddy");
// These point to the SAME object:
console.log(Object.getPrototypeOf(myDog) === Dog.prototype); // true
// console.log(myDog.__proto__ === Dog.prototype); // true but non-standard
-
prototype
is a property of constructor functions -
__proto__
(orObject.getPrototypeOf()
) is how you access an instance's prototype - They reference the same object!
The Constructor Property
Every prototype has a constructor
property pointing back to the constructor function:
console.log(Dog.prototype.constructor === Dog); // true
console.log(myDog.constructor === Dog); // true (inherited from prototype)
console.log(myDog.constructor.name); // "Dog"
Prototypal Inheritance
Now let's create inheritance relationships between different object types.
Setting Up Inheritance
Here's the recommended approach for creating inheritance:
// Parent constructor
function Vehicle(type) {
this.type = type;
this.wheels = 0;
}
Vehicle.prototype.move = function() {
return `The ${this.type} is moving`;
};
Vehicle.prototype.getInfo = function() {
return `Type: ${this.type}, Wheels: ${this.wheels}`;
};
// Child constructor
function Car(make, model, year) {
// Call parent constructor with current context
Vehicle.call(this, "car");
this.make = make;
this.model = model;
this.year = year;
this.wheels = 4;
}
// Set up inheritance - RECOMMENDED WAY
Car.prototype = Object.create(Vehicle.prototype);
Car.prototype.constructor = Car; // Restore constructor reference
// Add child-specific methods
Car.prototype.drive = function() {
return `Driving the ${this.year} ${this.make} ${this.model}`;
};
const myCar = new Car("Toyota", "Camry", 2024);
console.log(myCar.drive()); // "Driving the 2024 Toyota Camry"
console.log(myCar.move()); // "The car is moving" (inherited)
console.log(myCar.getInfo()); // "Type: car, Wheels: 4"
⚠️ Performance Note: Avoid Object.setPrototypeOf()
on existing objects as it breaks JavaScript engine optimizations. Use Object.create()
during setup instead.
Understanding call()
in Inheritance
The Vehicle.call(this, "car")
line is crucial for proper inheritance:
// call() sets the context (this) for the function
// Without call():
Vehicle("car"); // this would be the global object, not our car instance
// With call():
Vehicle.call(this, "car"); // this refers to the new car instance
Visualizing the Prototype Chain
Our inheritance creates this chain:
myCar instance
↓ [[Prototype]]
Car.prototype (has drive method)
↓ [[Prototype]]
Vehicle.prototype (has move, getInfo methods)
↓ [[Prototype]]
Object.prototype (has toString, valueOf methods)
↓ [[Prototype]]
null (end of chain)
Object.create(): Direct Prototype Control
Object.create()
provides an alternative way to set up inheritance without constructor functions:
// Create a base object
const vehicle = {
type: "vehicle",
wheels: 0,
move() {
return `The ${this.type} is moving`;
}
};
// Create objects that inherit from vehicle
const car = Object.create(vehicle);
car.type = "car";
car.wheels = 4;
car.drive = function() {
return `The ${this.type} is driving`;
};
const bike = Object.create(vehicle);
bike.type = "bike";
bike.wheels = 2;
bike.pedal = function() {
return `The ${this.type} is pedaling`;
};
console.log(car.move()); // "The car is moving" (inherited)
console.log(car.drive()); // "The car is driving" (own method)
console.log(bike.move()); // "The bike is moving" (inherited)
console.log(bike.pedal()); // "The bike is pedaling" (own method)
When to Use What Approach
Use Case | Best Approach |
---|---|
Single objects | Object literals {}
|
Multiple similar instances | Constructor functions |
Simple inheritance | Object.create() |
Complex inheritance | ES6 classes |
Legacy browser support | Constructor functions |
Property Lookup and Shadowing
Understanding how JavaScript finds properties is essential for debugging and optimization.
How Property Lookup Works
When you access a property, JavaScript searches in this order:
- The object itself - Does it have this property directly?
- The object's prototype - Does the prototype have it?
-
Up the chain - Keep checking each prototype until found or reaching
null
const myCar = new Car("Toyota", "Camry", 2024);
// Looking for myCar.move():
// 1. Check myCar object directly - not found
// 2. Check Car.prototype - not found
// 3. Check Vehicle.prototype - found! ✓
console.log(myCar.move()); // "The car is moving"
// The search stops at the FIRST match found
console.log(myCar.hasOwnProperty("type")); // true - stops here, doesn't check prototype
Property Shadowing
Child objects can "shadow" (override) parent properties, and the lookup stops at the first match:
// Parent method
Vehicle.prototype.start = function() {
return "Vehicle starting...";
};
// Child method shadows parent - lookup stops here
Car.prototype.start = function() {
return "Car engine starting...";
};
const myCar = new Car("Toyota", "Camry", 2024);
console.log(myCar.start()); // "Car engine starting..." (child version)
// To verify shadowing:
console.log(Car.prototype.hasOwnProperty("start")); // true
console.log(Vehicle.prototype.hasOwnProperty("start")); // true
// But myCar.start() only finds the first one (Car's version)
Inspecting Objects and Prototypes
JavaScript provides powerful tools for examining object relationships.
Understanding Instance vs Inherited Properties
const myCar = new Car("Toyota", "Camry", 2024);
// Check if property belongs to the instance (not inherited)
console.log(myCar.hasOwnProperty("make")); // true - instance property
console.log(myCar.hasOwnProperty("drive")); // false - inherited from prototype
// Property access works regardless of where it's defined
console.log(myCar.make); // "Toyota" (instance property)
console.log(myCar.drive()); // "Driving the 2024 Toyota Camry" (prototype method)
Important: Property lookup always returns the first match found, so shadowing stops the search even if parent prototypes have the same property name.
Checking Inheritance Relationships
JavaScript provides several ways to examine prototype relationships:
const myCar = new Car("Toyota", "Camry", 2024);
// instanceof - checks if prototype appears anywhere in chain
console.log(myCar instanceof Car); // true
console.log(myCar instanceof Vehicle); // true
console.log(myCar instanceof Object); // true
// isPrototypeOf - checks direct prototype relationship
console.log(Car.prototype.isPrototypeOf(myCar)); // true
console.log(Vehicle.prototype.isPrototypeOf(myCar)); // true
console.log(Object.prototype.isPrototypeOf(myCar)); // true
// constructor property - points to the constructor function
console.log(myCar.constructor === Car); // true
console.log(myCar.constructor.name); // "Car"
// Recommended way to access prototype
console.log(Object.getPrototypeOf(myCar) === Car.prototype); // true
// Non-standard way (avoid in production):
// console.log(myCar.__proto__ === Car.prototype); // true but deprecated
Debugging Helper Function
Here's a useful function for exploring prototype chains:
function inspectPrototypeChain(obj) {
let current = obj;
let level = 0;
console.log("Prototype chain:");
while (current) {
console.log(`Level ${level}:`, current.constructor?.name || 'Object');
current = Object.getPrototypeOf(current);
level++;
if (level > 10) break; // Safety check
}
}
inspectPrototypeChain(myCar);
// Output:
// Level 0: Car
// Level 1: Vehicle
// Level 2: Object
// Level 3: null
Modern ES6 Classes
ES6 introduced class syntax, which is "syntactic sugar" over the prototype system we've been exploring.
ES6 Class Syntax
// ES6 Class syntax (modern)
class Vehicle {
constructor(type) {
this.type = type;
this.wheels = 0;
}
move() {
return `The ${this.type} is moving`;
}
getInfo() {
return `Type: ${this.type}, Wheels: ${this.wheels}`;
}
}
class Car extends Vehicle {
constructor(make, model, year) {
super("car"); // Equivalent to Vehicle.call(this, "car")
this.make = make;
this.model = model;
this.year = year;
this.wheels = 4;
}
drive() {
return `Driving the ${this.year} ${this.make} ${this.model}`;
}
}
// Usage is identical
const myCar = new Car("Toyota", "Camry", 2024);
console.log(myCar.drive()); // "Driving the 2024 Toyota Camry"
console.log(myCar.move()); // "The car is moving"
Under the hood, this creates the exact same prototype relationships as our constructor function examples!
Class vs Constructor Function Comparison
Feature | Constructor Functions | ES6 Classes |
---|---|---|
Syntax | More verbose | Cleaner, familiar |
Inheritance setup | Manual Object.create()
|
Built-in extends
|
Super calls | Manual .call()
|
Built-in super()
|
Browser support | Universal | Modern browsers |
Performance | Same | Same (it's just syntax) |
Best Practices and Quick Reference {#best-practices}
Best Practices
-
Use
Object.create()
for inheritance setup - More performant thanObject.setPrototypeOf()
-
Always restore constructor property - When manually setting prototypes:
Child.prototype.constructor = Child
-
Use
Object.getPrototypeOf()
- Instead of the non-standard__proto__
property - Capitalize constructor names - Convention to distinguish from regular functions
- Add methods to prototype - More memory efficient than adding to constructor
- Consider ES6 classes - Cleaner syntax, same underlying mechanics
-
Avoid
Object.setPrototypeOf()
on existing objects - Can break JavaScript engine optimizations
Quick Reference
// Check prototype relationships
obj instanceof Constructor // true if Constructor.prototype in obj's chain
Constructor.prototype.isPrototypeOf(obj) // Same as above
obj.constructor === Constructor // true if obj created by Constructor
// Access prototype (recommended)
Object.getPrototypeOf(obj) // Standard, recommended way
// obj.__proto__ // Non-standard, avoid in production
// Check property ownership
obj.hasOwnProperty("propName") // true if property belongs to obj (not inherited)
// Set up inheritance (recommended approach)
Child.prototype = Object.create(Parent.prototype);
Child.prototype.constructor = Child; // Restore constructor reference
Key Takeaways
- Objects are the foundation - Everything in JavaScript is an object or inherits from Object.prototype
- Constructors are object factories - They create multiple objects with shared structure
- Prototypes enable sharing - Methods and properties can be shared across all instances
- Inheritance creates hierarchies - Objects can inherit from other objects, forming chains
- The prototype chain enables method lookup - JavaScript searches up the chain to find properties
-
prototype
vsObject.getPrototypeOf()
- Constructor property vs standard accessor method - All roads lead to Object.prototype - The ultimate parent of (almost) all JavaScript objects
Conclusion
Understanding prototypes is fundamental to mastering JavaScript. While ES6 classes provide a more familiar syntax, the underlying prototype system powers everything. With this knowledge, you can:
- Create efficient object hierarchies
- Debug inheritance issues
- Write more performant JavaScript code
- Understand how JavaScript libraries work under the hood
The prototype system is what makes JavaScript unique and powerful. Once you understand these concepts, you'll be able to leverage JavaScript's flexibility to create elegant, efficient solutions to complex problems.
Ready to practice? Try creating your own inheritance hierarchy using both constructor functions and ES6 classes to reinforce these concepts!
This content originally appeared on DEV Community and was authored by Dehemi Fabio

Dehemi Fabio | Sciencx (2025-08-09T09:32:05+00:00) A complete guide to Prototypes, Constructors and Inheritance in JavaScript. Retrieved from https://www.scien.cx/2025/08/09/a-complete-guide-to-prototypes-constructors-and-inheritance-in-javascript/
Please log in to upload a file.
There are no updates yet.
Click the Upload button above to add an update.