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

OohnohassaniOohnohassani
16 min read

Table of contents

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โ€™s name 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 the Person blueprint.

  • It will have name = 'Hassani', age = 25, and can use the shared greet() 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 extends Device, 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

PillarReal-Life ExampleCode Concept
AbstractionPressing 1 button to take a photoHiding complex inner workings
EncapsulationPrivate screen time on phoneKeeping data/methods protected
InheritanceCamera inherits device featuresExtending a base class
Polymorphismโ€œPlayโ€ works differently on devicesMethod 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:

FeatureProceduralObject-Oriented
StructureScattered codeOrganized in objects
ReusabilityHard to reuseEasy to reuse and extend
Data SafetyAnyone can modify global dataEncapsulation keeps it protected
ScalabilityMessy in large appsGreat for large, growing projects
Real-world ModelingHarderMore 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 call capture(), 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 the image and isCameraOpen 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 like open(), capture(), applyFilter(), and upload().

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, the Camera class inherits the powerOn() method from the Device class. This helps us avoid rewriting code for common functionality. We just extend it!
The keyword super() 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:
Both Camera and Phone have the takePicture() 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:

PillarConceptES6 Class Example
AbstractionHiding internal details, exposing only necessary infoCamera class with public methods like capture()
EncapsulationKeeping data safe and privateimage protected inside the class, accessed through getter
InheritanceReusing functionality through parent-child classesCamera inherits powerOn() from Device
PolymorphismSame method, different behavior depending on objecttakePicture() 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! ๐Ÿ™Œ

0
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 ๐Ÿš€