OOP in JavaScript
JavaScript’s class keyword looks like C#, but underneath it is still prototypes. Knowing what happens under the hood explains its quirks — and how to share behaviour without inheritance.
On this page · 6 min · Advanced
On this page
- In simple words
- Learning objectives
- Explanation
- Code example
- Step by step
- Real-world use
- Common mistakes
- Best practices
- Remember this
- Practice exercise
- Mini quiz
- Related topics
- My notes
- Reading time
- 6 min
- Difficulty
- Advanced
- Updated
- Oct 28, 2026
Related tutorials
In simple words#
JavaScript classes are a friendly wrapper around prototypes — like a new label on an old, reliable machine.
- class
- Syntax for constructor functions and prototypes.
- Prototype
- The object others inherit from in JS.
- #private
- Truly private fields in JS classes.
Learning objectives#
- Explain that JavaScript classes are built on prototypes
- Compare constructor functions with class syntax
- Share behaviour with mixins
- Choose between classes, object literals and factory functions
Explanation#
Every JavaScript object has a hidden link to another object, its prototype. If a property is not found on the object, JavaScript looks at the prototype, then its prototype — the prototype chain.
class (ES2015) is a cleaner way to write constructor functions and prototype methods. typeof SomeClass is "function", and methods live on SomeClass.prototype, shared by all instances.
Modern classes also have #private fields, static members, getters/setters and extends/super — everything this track has used.
JavaScript has single inheritance, but you can mix in behaviour with mixins: functions that take a class and return an extended class.
Not everything needs a class. Object literals ({ x: 1, toString() {...} }) and factory functions that return objects are idiomatic and often simpler.
Code example: Prototypes, classes and mixins#
// 1. The old way: a constructor function + prototype
function OldStyleUser(name) {
this.name = name;
}
OldStyleUser.prototype.greet = function () {
return `Hi, I am ${this.name}`;
};
// 2. The class way: same thing, nicer syntax
class User {
constructor(name) { this.name = name; }
greet() { return `Hi, I am ${this.name}`; }
}
console.log(new OldStyleUser('Asha').greet());
console.log(new User('Ravi').greet());
console.log(typeof User);
console.log(Object.getPrototypeOf(new User('x')) === User.prototype);
// 3. Mixin: add behaviour without a shared parent class
const Timestamped = Base => class extends Base {
stamp() { return `${this.name} @ 2026-10-28`; }
};
class Admin extends Timestamped(User) {}
console.log(new Admin('Meera').stamp());
// 4. Object literal: an object without any class
const point = { x: 1, y: 2, toString() { return `(${this.x}, ${this.y})`; } };
console.log(`${point}`);
Hi, I am Asha Hi, I am Ravi function true Meera @ 2026-10-28 (1, 2)
Step-by-step explanation#
- OldStyleUser and User behave the same: both put greet on the prototype, shared by every instance.
- typeof User is "function" — a class is a special kind of function.
- An instance’s prototype is User.prototype; that is where JavaScript finds greet.
- Timestamped is a mixin: it takes any class and returns a subclass with an extra method.
- point is a plain object with its own toString — no class needed.
Real-world use case#
React moved from class components to functions and hooks, but libraries such as Angular, NestJS, TypeORM and Phaser are class-based. Knowing both styles lets you read any JavaScript codebase.
Common mistakes#
JavaScript classes have no overloading, no interfaces and no access modifiers except #private. Use TypeScript if you want more compile-time checks.
this.greet = function () {...} creates a new function for every object. Define methods in the class body so they are shared on the prototype.
Adding methods to Array.prototype can break other code and future JavaScript versions. Write a helper function instead.
Best practices#
- Use class syntax for OOP in JavaScript
- Use #private for real encapsulation
- Prefer composition and mixins over deep extends chains
- Use plain objects and functions when no shared behaviour is needed
Remember this#
class = nicer syntax for prototypes.
Methods live on the prototype, shared by all instances.
Mixin = function that takes a class and returns a richer class.
Practice exercise#
Your task: Write a Serializable mixin that adds toJSONString() (returns JSON.stringify(this)) to any class. Apply it to a Product class with name and price and log the result.
Need a hint?
Show solution
const Serializable = Base => class extends Base {
toJSONString() { return JSON.stringify(this); }
};
class Product {
constructor(name, price) { this.name = name; this.price = price; }
}
class SerializableProduct extends Serializable(Product) {}
console.log(new SerializableProduct('Pen', 20).toJSONString()); // {"name":"Pen","price":20}
Mini quiz#
What is logged?
class A {}
console.log(typeof A);
Want more? Practise all OOP Concepts MCQs · OOP Concepts interview questions
My notes#
Saved in this browser.