JavaScript OOP and Classes Made Simple: A Fun, Beginner-Friendly Breakdown ๐๐ก

Table of contents
- โ What is Object-Oriented Programming (OOP)?
- ๐ OOP Before ES6: Constructor Functions
- ๐ง Mini Glossary
- ๐ฏ The 4 Pillars Again โ Using ES6 Classes (Modern JavaScript)
- ๐ง Why Use OOP in JavaScript?
- ๐งช Quick Quiz! (No Pressure ๐)
- โ๏ธ Procedural Programming vs Object-Oriented Programming
- ๐ฅ Real-World Example: A Camera App
- ๐ง Why OOP is Preferred (Especially in Bigger Projects)
- ๐ Revisited: The 4 Pillars of OOP with ES6 Classes
- ๐ Summary of the 4 Pillars of OOP:

Hey coder friend ๐,
I know how confusing JavaScript OOP (Object-Oriented Programming) can feel at first. Iโve been there too โ staring at code, rereading tutorials, watching videos over and over, wondering, "Wait, what even IS abstraction?" ๐ตโ๐ซ
Trust me, this stuff doesnโt always click on the first try. It might take a few weeks, months, or even longer. And thatโs perfectly okay ๐. Eventually, these concepts start to make sense, and youโll look back and smile at how far youโve come.
Letโs walk through it all step by step โ using simple language, relatable examples, and of course, emojis to help us visualize things ๐ง โจ
โ What is Object-Oriented Programming (OOP)?
Object-Oriented Programming is a way of writing code where we group related data and behavior (functions) into objects. Think of it like packing all the related things into a single box ๐ฆ.
Instead of writing many scattered variables and functions, we create reusable objects that represent real-world things โ like a Car
, User
, or Animal
.
Youโve probably already used objects like this:
const person = {
name: 'Ali',
age: 30,
greet() {
console.log(`Hi, I'm ${this.name}`);
}
};
But OOP takes it to the next level with blueprints called constructor functions and classes.
๐ OOP Before ES6: Constructor Functions
Before JavaScript got classes in ES6, we used constructor functions to create object blueprints.
๐ง Constructor Function Example:
function Person(name, age) {
this.name = name;
this.age = age;
}
Person.prototype.greet = function() {
console.log(`Hello, my name is ${this.name}`);
};
const user1 = new Person('Hassani', 25);
user1.greet();
๐ป Console Output:
Hello, my name is Hassani
๐จโ๐ซ Whatโs Happening Here? (Explained in Depth)
Letโs break this down line by line, like weโre talking to a total beginner ๐ฌ
function Person(name, age) {
๐น This defines a constructor function named Person
. Think of this like a recipe ๐ for baking a cake โ you can use it over and over to create as many cakes (people) as you want.
It accepts two ingredients (parameters): name
and age
.
this.name = name;
this.age = age;
๐น Hereโs where the magic happens ๐ช:
this.name
= name;
means: "The new objectโsname
property should be whatever was passed in."this.age = age;
works the same for age.
If you do new Person('Hassani', 25)
, you're basically creating:
{
name: 'Hassani',
age: 25
}
this
refers to the object thatโs being created when we use new
.
}
๐น This closes the function. You now have a constructor that can make people!
Person.prototype.greet = function() {
console.log(`Hello, my name is ${this.name}`);
};
๐น This adds a method to the Person
prototype. But why? ๐ค
If you added greet()
directly inside the constructor, each object would get its own copy โ not efficient!
So we attach it to the prototype. Now, all Person
objects share one single greet function. Smart! ๐ก
const user1 = new Person('Hassani', 25);
๐น We create a person! ๐งโโ๏ธ
user1
becomes a new object based on thePerson
blueprint.It will have
name = 'Hassani'
,age = 25
, and can use the sharedgreet()
method.
user1.greet();
๐น We call the method. JavaScript sees user1
doesnโt have greet()
directly, so it looks up the chain (prototype) and finds it there ๐.
The console shows:
Hello, my name is Hassani
Next up: Weโll now dive into the 4 Pillars of OOP โ Abstraction, Encapsulation, Inheritance, and Polymorphism โ first using constructor functions, and then using modern ES6 classes!
You donโt need to memorize everything perfectly. Just absorb the ideas slowly ๐ฑ
๐ง Mini Glossary
To help you along the way:
Class โ A blueprint/template to create objects.
Object โ An actual thing created from a class (an instance).
Constructor โ A special function that runs when you make a new object.
Method โ A function that lives inside an object.
Prototype โ A hidden object that helps share methods across instances.
Instance โ A specific object made from a class (e.g.
const phone = new Phone()
).Private (#) โ Keeps a variable or method safe inside the class.
Letโs continue the blog now and dive into the ES6 Classes section, using your iPhone/Camera analogy to explain Abstraction, Encapsulation, Inheritance, and Polymorphism in a way that truly sticks.
๐ฏ The 4 Pillars Again โ Using ES6 Classes (Modern JavaScript)
JavaScript introduced classes in ES6, and they made OOP much cleaner and easier to understand. Theyโre basically syntactic sugar ๐ฌ over constructor functions and prototypes โ just a nicer way to write the same thing.
Letโs revisit the 4 pillars, but this time, weโll use a more real-world analogy to explain each one:
๐ง Think of a smartphone (like an iPhone) or a digital camera as our object!
๐ต๏ธโโ๏ธ 1. Abstraction โ Hiding Complex Stuff
Think of your iPhone or Camera. ๐ธ
You press a button to take a picture โ but inside, so much is happening: focusing the lens, adjusting the light, processing the image.
Do you need to know how all that works? Nope. Just press one button and boom โ selfie! ๐ท
Thatโs abstraction: showing only whatโs necessary, hiding the rest.
class Camera {
constructor(brand) {
this.brand = brand;
}
#adjustSettings() {
console.log("๐ง Adjusting aperture, ISO, and shutter speed...");
}
takePhoto() {
this.#adjustSettings(); // internal details are hidden
console.log(`๐ธ Click! Photo taken with ${this.brand}`);
}
}
const myCamera = new Camera("Canon");
myCamera.takePhoto();
๐ฅ Output:
๐ง Adjusting aperture, ISO, and shutter speed...
๐ธ Click! Photo taken with Canon
#adjustSettings()
is private โ the user can't call it directly.They just use
takePhoto()
, and everything else is abstracted away.
๐ก๏ธ 2. Encapsulation โ Keeping Data Safe
Letโs say you have a phone that tracks your screen time. You donโt want an app messing with the screen time data, right?
Encapsulation is like putting your important data behind a lock ๐ โ only certain methods can access or change it.
class Phone {
#screenTime = 0;
use(hours) {
if (hours > 0) {
this.#screenTime += hours;
}
}
getScreenTime() {
return `${this.#screenTime} hours`;
}
}
const myPhone = new Phone();
myPhone.use(2);
console.log(myPhone.getScreenTime()); // 2 hours
console.log(myPhone.#screenTime); // โ Error! Private property
๐ฅ Output:
2 hours
Uncaught SyntaxError: Private field '#screenTime' must be declared in an enclosing class
- The
#screenTime
is private and can't be accessed from outside. Thatโs encapsulation.
๐ช 3. Inheritance โ Child Gets Traits from Parent
Just like a camera app inherits features from your phone (like storage, battery), classes can inherit from other classes.
Letโs build a base Device
class and have other gadgets inherit from it!
class Device {
constructor(name) {
this.name = name;
}
powerOn() {
console.log(`${this.name} is now ON ๐`);
}
}
class Camera extends Device {
takePhoto() {
console.log(`${this.name} takes a photo ๐ธ`);
}
}
const canon = new Camera("Canon G7X");
canon.powerOn(); // inherited method
canon.takePhoto(); // its own method
๐ฅ Output:
Canon G7X is now ON ๐
Canon G7X takes a photo ๐ธ
Camera
extendsDevice
, inheriting its methods.In real life, your camera inherited power-on ability from being a device ๐ฑ๐.
๐ 4. Polymorphism โ Same Method, Different Behavior
Imagine pressing the "Play" button:
On a phone: plays music ๐ต
On a camera: plays a slideshow ๐ผ๏ธ
On a smart TV: plays a video ๐ฌ
Same button, different result depending on the device.
Letโs see this in code:
class Device {
play() {
console.log("Playing something ๐ฎ");
}
}
class Phone extends Device {
play() {
console.log("๐ฑ Playing music");
}
}
class Camera extends Device {
play() {
console.log("๐ธ Playing slideshow");
}
}
const myPhone = new Phone();
const myCamera = new Camera();
myPhone.play(); // ๐ฑ Playing music
myCamera.play(); // ๐ธ Playing slideshow
๐ฅ Output:
๐ฑ Playing music
๐ธ Playing slideshow
Thatโs polymorphism โ the same method play()
behaves differently depending on the class thatโs using it.
๐ก Wrap-Up of the 4 Pillars
Pillar | Real-Life Example | Code Concept |
Abstraction | Pressing 1 button to take a photo | Hiding complex inner workings |
Encapsulation | Private screen time on phone | Keeping data/methods protected |
Inheritance | Camera inherits device features | Extending a base class |
Polymorphism | โPlayโ works differently on devices | Method overrides in child classes |
๐ง Why Use OOP in JavaScript?
So... why should you care about all this Object-Oriented Programming stuff?
Hereโs why developers (like you ๐) love OOP โ especially when building large projects:
๐งผ 1. Clean Code
OOP helps organize your code neatly into objects and classes.
You avoid writing spaghetti code ๐ and instead, build readable, structured blocks.
// Without OOP
let name = "Canon";
let battery = 50;
function takePhoto() {
// ...
}
// With OOP
class Camera {
constructor(name) {
this.name = name;
this.battery = 50;
}
takePhoto() {
// ...
}
}
๐ 2. Reusability
With inheritance, you can reuse common logic in many places.
Build once, use everywhere. No copy-pasting code again and again.
class Device {
powerOn() {
console.log("Powering on...");
}
}
class Phone extends Device {} // Phone inherits powerOn()
(๐ extends
means the class is inheriting from another one.)
๐ 3. Better Security (Encapsulation)
You can protect data by keeping it private (like #password
, #pin
).
This prevents bugs and misuse.
class Account {
#pin = 1234;
checkPin(input) {
return input === this.#pin;
}
}
(๐ The #
before a variable means itโs private and not accessible from outside.)
โ๏ธ 4. Powerful Flexibility (Polymorphism)
Different classes can use the same method name but do things their own way.
class Animal {
speak() {
console.log("Animal sound");
}
}
class Dog extends Animal {
speak() {
console.log("Bark ๐ถ");
}
}
(๐ This is polymorphism, and it helps when many objects need similar methods but with their own behavior.)
๐ง 5. Based on Real-World Concepts
OOP is modeled after real things.
Think of:
A User
A Photo
A Product
A Button
Each has properties (like name
, price
) and methods (like click()
, addToCart()
).
This makes your code feel natural and easier to design.
๐งช Quick Quiz! (No Pressure ๐)
Let's see how much you've picked up so far.
Grab a pen and try answering before checking the answers! ๐๏ธโ๏ธ
๐งฉ Question 1:
What does this line do?
const user = new Person('Hassani', 25);
A. It deletes a person called Hassani
B. It creates a new object using the Person blueprint
C. It assigns 25 to a global variable
D. It runs a for loop
๐งฉ Question 2:
Which keyword helps classes inherit from another class?
A. new
B. prototype
C. extends
D. superpowers
๐งฉ Question 3:
Which of these is an example of encapsulation?
A. Sharing all your variables with the whole app
B. Keeping methods inside a class
C. Writing only console logs
D. Using alerts in every file
๐งฉ Question 4:
Whatโs the main benefit of using the prototype?
A. To create bugs
B. To copy code to every object
C. To share methods across objects without duplicating them
D. To hack into the matrix
How did you do? ๐ If you got even 1 or 2 right, thatโs a win!
If not โ donโt stress. Weโre all learning together ๐ช
Now that weโve had our little review, letโs move on and see why developers often prefer OOP over the traditional (procedural) way of writing code...
Awesome! Letโs jump right in and explore Procedural Programming vs Object-Oriented Programming โ and letโs make it fun and visual using a relatable example: a ๐ธ Camera App! (or you can imagine a Phone or a Bakery, but weโll go with the Camera for now since it works great for this concept).
โ๏ธ Procedural Programming vs Object-Oriented Programming
Before we dive into code, letโs first understand what these two styles really mean in plain English:
๐งพ Procedural Programming (PP)
This is the traditional style of coding โ itโs like giving your computer a step-by-step recipe ๐.
You write functions and variables that operate separately.
Think of it like this:
โHey computer, take this image, resize it, apply a filter, compress it, then upload it.โ
Everything is done step by step, but data and functions are kept separate.
๐งฑ Object-Oriented Programming (OOP)
OOP bundles everything together into objects that represent real-world things.
Itโs like saying:
โHey camera object, please take a photo, apply a filter, and upload it.โ
You group related data + behavior inside an object. Neat and clean.
๐ฅ Real-World Example: A Camera App
Letโs compare both styles by imagining weโre building a basic photo app.
๐งพ Procedural Way (Messy and Scattered)
// Data
let image = null;
let isCameraOpen = false;
// Functions
function openCamera() {
isCameraOpen = true;
console.log('Camera opened');
}
function captureImage() {
if (isCameraOpen) {
image = '๐ธ Image captured!';
console.log(image);
}
}
function applyFilter(img, filterName) {
console.log(`Applying ${filterName} to ${img}`);
}
function uploadImage(img) {
console.log(`Uploading: ${img}`);
}
// Execution
openCamera();
captureImage();
applyFilter(image, 'Black & White');
uploadImage(image);
๐ซ Problem?
Everything is scattered ๐งฉ
Functions depend on global variables ๐ฌ
Hard to manage as the app grows
Anyone can change
image
by mistake (no protection)
๐งฑ Object-Oriented Way (Organized and Scalable)
class Camera {
constructor() {
this.image = null;
this.isCameraOpen = false;
}
open() {
this.isCameraOpen = true;
console.log('Camera opened');
}
capture() {
if (this.isCameraOpen) {
this.image = '๐ธ Image captured!';
console.log(this.image);
}
}
applyFilter(filterName) {
if (this.image) {
console.log(`Applying ${filterName} to ${this.image}`);
}
}
upload() {
if (this.image) {
console.log(`Uploading: ${this.image}`);
}
}
}
// Using the Camera
const myCamera = new Camera();
myCamera.open();
myCamera.capture();
myCamera.applyFilter('Black & White');
myCamera.upload();
โ Much better!
Code is clean and grouped ๐
Easy to reuse and scale
Data is protected inside the object
Looks more like how we think about things in real life ๐ฏ
๐ Recap:
Feature | Procedural | Object-Oriented |
Structure | Scattered code | Organized in objects |
Reusability | Hard to reuse | Easy to reuse and extend |
Data Safety | Anyone can modify global data | Encapsulation keeps it protected |
Scalability | Messy in large apps | Great for large, growing projects |
Real-world Modeling | Harder | More natural and intuitive |
๐ง Why OOP is Preferred (Especially in Bigger Projects)
Easier to manage complexity as your codebase grows ๐งฉ
You can create reusable components like
User
,Product
,Order
๐ฆReal-world modeling feels more natural and intuitive
Powerful concepts like inheritance, encapsulation, and polymorphism are built in
Better for working in teams and collaborative projects ๐ค
You're right to ask why we should revisit the pillars of OOP with classes โ itโs because now that weโve covered procedural programming and why OOP is so useful for larger projects, we can revisit the core principles of OOP using the modern ES6 class syntax. This will give readers a fresh perspective after learning about constructor functions earlier on. ๐ง
The ES6 class syntax is cleaner, easier to understand, and much more readable compared to the older style. So, letโs take a closer look at the 4 Pillars of OOP and demonstrate them using ES6 Classes.
๐ Revisited: The 4 Pillars of OOP with ES6 Classes
Weโre going to use Camera examples once again to make things feel practical and fun. ๐ฅ
1. ๐ต๏ธโโ๏ธ Abstraction โ Hiding the Complex Stuff
Abstraction is all about hiding unnecessary details from the user and only exposing whatโs needed. It simplifies things.
In our Camera App, we donโt need to show the user how the camera works under the hood. We just want them to use simple functions like takePicture()
or applyFilter()
.
class Camera {
constructor() {
this.image = null;
this.isCameraOpen = false;
}
open() {
this.isCameraOpen = true;
console.log('Camera opened');
}
capture() {
if (this.isCameraOpen) {
this.image = '๐ธ Image captured!';
console.log(this.image);
}
}
applyFilter(filterName) {
console.log(`Applying ${filterName} to ${this.image}`);
}
upload() {
console.log(`Uploading: ${this.image}`);
}
}
// Usage
const myCamera = new Camera();
myCamera.open();
myCamera.capture();
myCamera.applyFilter('Black & White');
myCamera.upload();
๐ง Explanation:
In this code, the user doesnโt need to know how the camera actually works behind the scenes (like focusing the lens, adjusting the aperture, etc.). They just callcapture()
, and everything else happens internally.
2. ๐ก๏ธ Encapsulation โ Protecting the Data
Encapsulation is about keeping data safe inside the object and only allowing access through public methods. This helps prevent accidental changes.
class Camera {
constructor() {
this.image = null; // Private data
this.isCameraOpen = false; // Private data
}
open() {
this.isCameraOpen = true;
console.log('Camera opened');
}
capture() {
if (this.isCameraOpen) {
this.image = '๐ธ Image captured!';
console.log(this.image);
}
}
applyFilter(filterName) {
console.log(`Applying ${filterName} to ${this.image}`);
}
upload() {
console.log(`Uploading: ${this.image}`);
}
// A public method to access image (getter)
getImage() {
return this.image;
}
}
// Usage
const myCamera = new Camera();
myCamera.open();
myCamera.capture();
console.log(myCamera.getImage()); // Access through a method (getter)
๐ง Explanation:
We kept theimage
andisCameraOpen
properties private. They are only accessible within the class itself. The user cannot directly modify these values. They can only interact with the camera through public methods likeopen()
,capture()
,applyFilter()
, andupload()
.
3. ๐ช Inheritance โ Reusing Code
Inheritance allows one class to inherit properties and methods from another class. This is helpful for building on top of existing functionality.
For example, imagine we have a base class called Device
, and our Camera
can inherit from it.
class Device {
constructor(name) {
this.name = name;
}
powerOn() {
console.log(`${this.name} is now ON!`);
}
}
class Camera extends Device {
constructor(name) {
super(name); // Call the parent class constructor
this.isCameraOpen = false;
this.image = null;
}
open() {
this.isCameraOpen = true;
console.log(`${this.name} camera opened`);
}
capture() {
if (this.isCameraOpen) {
this.image = '๐ธ Image captured!';
console.log(this.image);
}
}
}
// Usage
const myCamera = new Camera('Nikon');
myCamera.powerOn(); // Inherited method
myCamera.open();
myCamera.capture();
๐ง Explanation:
In this example, theCamera
class inherits thepowerOn()
method from theDevice
class. This helps us avoid rewriting code for common functionality. We just extend it!
The keywordsuper()
calls the constructor of the parent class (Device
).
4. ๐ Polymorphism โ Same Method, Different Behavior
Polymorphism allows different objects to respond to the same method in their own way. The method name is the same, but the behavior changes depending on the object.
Letโs say we want to have multiple devices (like a Phone
and a Camera
) that both have a takePicture()
method, but each device behaves differently when taking a picture.
class Device {
constructor(name) {
this.name = name;
}
takePicture() {
console.log('Taking a generic picture...');
}
}
class Camera extends Device {
constructor(name) {
super(name);
}
takePicture() {
console.log('๐ธ Camera taking a picture!');
}
}
class Phone extends Device {
constructor(name) {
super(name);
}
takePicture() {
console.log('๐ฑ Phone taking a selfie!');
}
}
// Usage
const camera = new Camera('Canon');
const phone = new Phone('iPhone');
camera.takePicture(); // Camera taking a picture!
phone.takePicture(); // Phone taking a selfie!
๐ง Explanation:
BothCamera
andPhone
have thetakePicture()
method, but the behavior is different for each. Thatโs polymorphism โ the same method name, but different behavior based on the object calling it. ๐ฅ
๐ Summary of the 4 Pillars of OOP:
Pillar | Concept | ES6 Class Example |
Abstraction | Hiding internal details, exposing only necessary info | Camera class with public methods like capture() |
Encapsulation | Keeping data safe and private | image protected inside the class, accessed through getter |
Inheritance | Reusing functionality through parent-child classes | Camera inherits powerOn() from Device |
Polymorphism | Same method, different behavior depending on object | takePicture() behaves differently for Camera vs Phone |
๐ Conclusion
Weโve covered the 4 key principles of OOP: Abstraction, Encapsulation, Inheritance, and Polymorphism, showing how they help organize code and make it easier to manage.
OOP is perfect for larger projects because it makes the code modular, scalable, and maintainable. By using these principles, youโll be able to write cleaner, more efficient code โ whether youโre working solo or in a team. ๐ป๐
With these concepts under your belt, you're now more familiar with how OOP helps in organizing code and how it scales in large projects. ๐ฏ
If anything still feels a bit tricky, don't worry! With practice, OOP will become second nature. Itโs a powerful tool that, once understood, can make your code much cleaner, maintainable, and easier to scale over time. ๐งโ๐ปโจ
Now itโs time to practice and apply what youโve learned. Youโve got this! ๐
Subscribe to my newsletter
Read articles from Oohnohassani directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by

Oohnohassani
Oohnohassani
Hi ๐ I'm a developer with experience in building websites with beautiful designs and user friendly interfaces. I'm experienced in front-end development using languages such as Html5, Css3, TailwindCss, Javascript, Typescript, react.js & redux and NextJs ๐