Object-Oriented Programming in JavaScript: Classes and Objects

Every JavaScript developer reaches a point where plain functions and objects start feeling messy. You have ten users in your app and you are creating each one manually, copy-pasting the same structure, hoping you do not forget a property. There has to be a better way.
That better way is Object-Oriented Programming — OOP. It is a way of organizing your code around real-world things rather than just a list of instructions.
What is OOP?
OOP is a programming style where you model your program around objects — things that have both data (properties) and behavior (methods).
Instead of writing a function to greet a user and a separate function to update their age and another function to display their profile — all floating around independently — OOP bundles all of that together into one unit that represents a user.
The core idea is that your code should mirror the way you think about the real world. A car has a color and a speed. It can accelerate and brake. A student has a name and an age. They can introduce themselves and submit an assignment. OOP lets you express these ideas directly in code.
The Blueprint Analogy
The single most useful way to understand OOP is the blueprint analogy.
An architect draws one blueprint for a house. That blueprint is not a house you can live in — it is just a plan. But from that one blueprint, you can build many actual houses. House A is blue with three bedrooms. House B is red with two bedrooms. They were both built from the same blueprint, but they are separate, independent houses.
BLUEPRINT (Class)
┌──────────────────┐
│ House │
│ ───────────── │
│ color │
│ bedrooms │
│ ───────────── │
│ describe() │
└────────┬─────────┘
│
builds many objects
│ │
▼ ▼
┌──────────────┐ ┌──────────────┐
│ houseA │ │ houseB │
│ ──────────── │ │ ──────────── │
│ color:"blue" │ │ color:"red" │
│ bedrooms: 3 │ │ bedrooms: 2 │
└──────────────┘ └──────────────┘
(an instance) (an instance)
In JavaScript:
The blueprint is a class
The actual houses built from it are objects (also called instances)
Building a house from the blueprint is called instantiation
Your First Class
In JavaScript, you define a class using the class keyword followed by a name. By convention, class names start with a capital letter.
class Car {
// class body goes here
}
This defines the blueprint. It does not create any car yet — it just describes what a car will look like when one is created.
To create an actual car from this blueprint, you use the new keyword:
class Car {
// empty for now
}
let myCar = new Car();
let yourCar = new Car();
console.log(myCar); // Car {}
console.log(yourCar); // Car {}
Both myCar and yourCar are separate objects created from the same Car class — just like two houses built from the same blueprint.
The Constructor Method
Right now the Car class creates empty objects. To give each car its own data when it is created, you use the constructor — a special method that runs automatically every time you create a new instance with new.
class Car {
constructor(brand, color, speed) {
this.brand = brand;
this.color = color;
this.speed = speed;
}
}
let car1 = new Car("Toyota", "red", 180);
let car2 = new Car("Honda", "blue", 200);
console.log(car1.brand); // "Toyota"
console.log(car1.color); // "red"
console.log(car1.speed); // 180
console.log(car2.brand); // "Honda"
console.log(car2.color); // "blue"
console.log(car2.speed); // 200
The this keyword refers to the specific object being created. When you write this.brand = brand, you are saying: "on this particular car being built right now, store the brand value."
Each object gets its own copy of the data. Changing car1 does not affect car2.
car1.color = "green";
console.log(car1.color); // "green"
console.log(car2.color); // "blue" — completely unaffected
Adding Methods
Methods are functions that belong to a class. They define what an object can do. You define them inside the class body, outside the constructor.
class Car {
constructor(brand, color, speed) {
this.brand = brand;
this.color = color;
this.speed = speed;
}
// A method — describes the car
describe() {
console.log(`\({this.brand} (\){this.color}) — top speed: ${this.speed} km/h`);
}
// A method — simulates accelerating
accelerate(amount) {
this.speed += amount;
console.log(`\({this.brand} sped up! New speed: \){this.speed} km/h`);
}
}
let car1 = new Car("Toyota", "red", 180);
let car2 = new Car("Honda", "blue", 200);
car1.describe(); // Toyota (red) — top speed: 180 km/h
car2.describe(); // Honda (blue) — top speed: 200 km/h
car1.accelerate(20); // Toyota sped up! New speed: 200 km/h
car1.describe(); // Toyota (red) — top speed: 200 km/h
car2.describe(); // Honda (blue) — top speed: 200 km/h — unchanged
Every object created from the Car class automatically gets access to describe() and accelerate(). You write the method once in the class and every instance can use it — that is reusability.
A Complete Example: Person
Let's build a slightly more practical example — a Person class:
class Person {
constructor(name, age, city) {
this.name = name;
this.age = age;
this.city = city;
}
greet() {
console.log(`Hi, I am \({this.name} from \){this.city}.`);
}
birthday() {
this.age += 1;
console.log(`Happy birthday \({this.name}! You are now \){this.age}.`);
}
introduce() {
console.log(`Name: \({this.name} | Age: \){this.age} | City: ${this.city}`);
}
}
let person1 = new Person("Arjun", 21, "Mumbai");
let person2 = new Person("Priya", 24, "Delhi");
let person3 = new Person("Rohan", 19, "Bangalore");
person1.greet(); // Hi, I am Arjun from Mumbai.
person2.greet(); // Hi, I am Priya from Delhi.
person3.greet(); // Hi, I am Rohan from Bangalore.
person1.birthday(); // Happy birthday Arjun! You are now 22.
person1.introduce(); // Name: Arjun | Age: 22 | City: Mumbai
// Create an array of person objects — very common real-world pattern
let people = [person1, person2, person3];
for (let person of people) {
person.introduce();
}
// Output:
// Name: Arjun | Age: 22 | City: Mumbai
// Name: Priya | Age: 24 | City: Delhi
// Name: Rohan | Age: 19 | City: Bangalore
One class, three objects, one loop. This is exactly what OOP is for — creating many instances of the same structure without repeating code.
Classes vs Manual Objects
To make the benefit concrete, here is the same data expressed both ways:
// Without a class — every object created manually
let user1 = { name: "Arjun", age: 21 };
let user2 = { name: "Priya", age: 24 };
let user3 = { name: "Rohan", age: 19 };
// No shared methods — you'd have to add greet() to each manually
// No guarantee they have the same shape
// Harder to maintain as the app grows
// ─────────────────────────────────────────────────────────
// With a class — one definition, consistent structure always
class User {
constructor(name, age) {
this.name = name;
this.age = age;
}
greet() {
console.log(`Hi, I am ${this.name}.`);
}
}
let user1 = new User("Arjun", 21);
let user2 = new User("Priya", 24);
let user3 = new User("Rohan", 19);
// Every user automatically has greet()
user1.greet(); // Hi, I am Arjun.
user2.greet(); // Hi, I am Priya.
user3.greet(); // Hi, I am Rohan.
Encapsulation — Keeping Things Together
Encapsulation is one of the core principles of OOP. The idea is simple: the data and the behavior that operates on that data should live in the same place.
Before OOP, you might have:
// Data and behavior are separate — easy to lose track of what belongs together
let studentName = "Priya";
let studentAge = 20;
function printStudentDetails(name, age) {
console.log(`\({name} is \){age} years old.`);
}
printStudentDetails(studentName, studentAge);
With encapsulation, the data (name, age) and the behavior (printStudentDetails) are bundled inside the same class:
// Data and behavior live together — one clean unit
class Student {
constructor(name, age) {
this.name = name;
this.age = age;
}
printDetails() {
console.log(`\({this.name} is \){this.age} years old.`);
}
}
let student = new Student("Priya", 20);
student.printDetails(); // Priya is 20 years old.
The function printDetails no longer needs to receive name and age as parameters — it already has access to them through this, because they are part of the same object. That is encapsulation: related things are enclosed in the same container.
Class → Instance Visual
CLASS (blueprint — defined once)
┌─────────────────────────────────┐
│ class Person { │
│ constructor(name, age, city) │
│ greet() │
│ introduce() │
│ } │
└──────────────┬──────────────────┘
│
new Person() │ called three times
┌─────────────┼─────────────┐
▼ ▼ ▼
┌──────────────┐ ┌────────────┐ ┌───────────────┐
│ person1 │ │ person2 │ │ person3 │
│ ──────────── │ │ ────────── │ │ ─────────── │ │
│ name:"Arjun" │ │name:"Priya"│ │name:"Rohan" │
│ age: 21 │ │ age: 24 │ │ age: 19 │
│ city:"Mumbai"│ │city:"Delhi"│ │city:"Bangalore│
│ │ │ │ │ │
│ greet() ✔ │ │ greet() ✔ │ │ greet() ✔ │
│ introduce() ✔│ │introduce()✔│ │introduce() ✔ │
└──────────────┘ └────────────┘ └───────────────┘
(instance 1) (instance 2) (instance 3)
Each instance has its own data but shares the methods defined in the class.
Assignment: Try It Yourself
Step 1: Create a Student class with a constructor
class Student {
constructor(name, age, course) {
this.name = name;
this.age = age;
this.course = course;
}
printDetails() {
console.log(`Name: \({this.name} | Age: \){this.age} | Course: ${this.course}`);
}
study(subject) {
console.log(`\({this.name} is studying \){subject}.`);
}
}
Step 2: Create multiple student objects
let student1 = new Student("Arjun", 21, "Computer Science");
let student2 = new Student("Priya", 20, "Mathematics");
let student3 = new Student("Rohan", 22, "Physics");
Step 3: Call methods on each object
student1.printDetails(); // Name: Arjun | Age: 21 | Course: Computer Science
student2.printDetails(); // Name: Priya | Age: 20 | Course: Mathematics
student3.printDetails(); // Name: Rohan | Age: 22 | Course: Physics
student1.study("Data Structures"); // Arjun is studying Data Structures.
Step 4: Update a property and verify
student1.age = 22;
student1.printDetails(); // Name: Arjun | Age: 22 | Course: Computer Science
Step 5: Loop through an array of students
let students = [student1, student2, student3];
console.log("All students:");
for (let student of students) {
student.printDetails();
}
// Output:
// All students:
// Name: Arjun | Age: 22 | Course: Computer Science
// Name: Priya | Age: 20 | Course: Mathematics
// Name: Rohan | Age: 22 | Course: Physics
Quick Recap
OOP organizes code around objects that combine data and behavior
A class is a blueprint — it defines the shape of an object but is not an object itself
An instance is an actual object created from a class using
newThe constructor runs automatically when a new instance is created and sets up its data
Methods are functions inside a class that every instance can use
thisrefers to the specific instance the method is running onEncapsulation means keeping related data and behavior together in one class
Comments
Join the discussionNo comments yet. Be the first to comment.
