A complete guide to Prototypes, Constructors and Inheritance in JavaScript

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 mu…


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

  1. The Foundation: JavaScript Objects
  2. Understanding Prototypes
  3. Constructor Functions
  4. Prototypal Inheritance
  5. Object.create() Method
  6. Property Lookup and Shadowing
  7. Inspecting Objects and Prototypes
  8. Modern ES6 Classes
  9. 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:

  • myArrayArray.prototypeObject.prototypenull
  • myStringString.prototypeObject.prototypenull

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__ (or Object.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:

  1. The object itself - Does it have this property directly?
  2. The object's prototype - Does the prototype have it?
  3. 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 than Object.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

  1. Objects are the foundation - Everything in JavaScript is an object or inherits from Object.prototype
  2. Constructors are object factories - They create multiple objects with shared structure
  3. Prototypes enable sharing - Methods and properties can be shared across all instances
  4. Inheritance creates hierarchies - Objects can inherit from other objects, forming chains
  5. The prototype chain enables method lookup - JavaScript searches up the chain to find properties
  6. prototype vs Object.getPrototypeOf() - Constructor property vs standard accessor method
  7. 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


Print Share Comment Cite Upload Translate Updates
APA

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/

MLA
" » A complete guide to Prototypes, Constructors and Inheritance in JavaScript." Dehemi Fabio | Sciencx - Saturday August 9, 2025, https://www.scien.cx/2025/08/09/a-complete-guide-to-prototypes-constructors-and-inheritance-in-javascript/
HARVARD
Dehemi Fabio | Sciencx Saturday August 9, 2025 » A complete guide to Prototypes, Constructors and Inheritance in JavaScript., viewed ,<https://www.scien.cx/2025/08/09/a-complete-guide-to-prototypes-constructors-and-inheritance-in-javascript/>
VANCOUVER
Dehemi Fabio | Sciencx - » A complete guide to Prototypes, Constructors and Inheritance in JavaScript. [Internet]. [Accessed ]. Available from: https://www.scien.cx/2025/08/09/a-complete-guide-to-prototypes-constructors-and-inheritance-in-javascript/
CHICAGO
" » A complete guide to Prototypes, Constructors and Inheritance in JavaScript." Dehemi Fabio | Sciencx - Accessed . https://www.scien.cx/2025/08/09/a-complete-guide-to-prototypes-constructors-and-inheritance-in-javascript/
IEEE
" » A complete guide to Prototypes, Constructors and Inheritance in JavaScript." Dehemi Fabio | Sciencx [Online]. Available: https://www.scien.cx/2025/08/09/a-complete-guide-to-prototypes-constructors-and-inheritance-in-javascript/. [Accessed: ]
rf:citation
» A complete guide to Prototypes, Constructors and Inheritance in JavaScript | Dehemi Fabio | Sciencx | 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.

You must be logged in to translate posts. Please log in or register.