img
Go back

Read

Published on: Feb 3, 2025

Programming

Object-Oriented Programming in Javascript and Typescript

There’s a never-ending argument about how OOP is inferior to functional programming, and vice versa. I didn’t understand the hype about functional programming until I learned to write the Elixir programming language, which was a beautiful realization that (of course) there’s a lot to learn about different programming paradigms.

Of course, there are other functional programming languages that I have used since then like Rust, Lua and C, but bad habits die hard… Everytime I would return to write OOP, sometimes even forcing a languages to work like that; my lowest point being trying to do OOP inside Lua scripting (It’s doable, but c’mon, at that point why the f*ck are you using a scripting language in the first place?)

So…

What is OOP?

Object-Oriented Programming is a paradigm that models software as a collection of data and objects. It is one of the most widely used paradigms in modern programming, forming the foundation for many programming languages and frameworks.

OOP is based on four main principles:

  1. Encapsulation – Group related data and behavior (methods) into objects.
  2. Abstraction – Hide complex implementation details behind simple interfaces.
  3. Inheritance – Allow new classes to inherit properties and methods from existing ones.
  4. Polymorphism – Let objects be treated as instances of their parent class rather than their actual class.

Conceptual Example:

Class: Animal
  - Properties: name, age
  - Methods: eat(), sleep()

Class: Dog extends Animal
  - Methods: bark()

OOP in JavaScript

Remember when I literally said this exact thing in this post few lines ago:

“at that point why the f*ck are you using a scripting language in the first place?”

Well, it’s time for me to be a hypocrite: OOP in Javascript not only works, but it makes big projects so much easier to scale!

JavaScript uses a prototype-based inheritance model under the hood, but since ES6 (ECMAScript 2015), it introduced “class” syntax to support more traditional OOP.

Classes and Instances

class Animal {
  constructor(name) {
    this.name = name;
  }

  speak() {
    console.log(`${this.name} makes a sound.`);
  }
}

const dog = new Animal('Rex');
dog.speak(); // Rex makes a sound.

Inheritance

class Dog extends Animal {
  speak() {
    console.log(`${this.name} barks.`);
  }
}

const d = new Dog('Buddy');
d.speak(); // Buddy barks.

Latelly I’ve seen a lot of hate with inheritance on the internet, some people claiming that the true answer to prevent bloated or repeated code is to use dependency injection, this paragraph literally doesn’t go anywhere, it’s only a self reminder to write a post about inheritance vs dependency injection in the future, sorry you read this post before I publish that!

Encapsulation via Private Fields

As of modern JavaScript (ES2022+), you can use ”#” to declare private fields. This is a pattern more often used alongside with setters and getters:

class BankAccount {
  #balance = 0;

  deposit(amount) {
    this.#balance += amount;
  }

  get_balance() {
    return this.#balance;
  }
}

It’s worth noting that this truly makes the value private at runtime. In TypeScript, the keyword “private” only enforces privacy at compile-time, helping to prevent mistakes during development.

Polymorphism

Polymorphism in JavaScript is often seen via method overriding and duck typing:

function make_sound(animal) {
  animal.speak();
}

make_sound(new Dog('Fido'));
make_sound(new Animal('Mr Beast'));

OOP in TypeScript

TypeScript is a statically typed superset of JavaScript. It enhances OOP with strong typing, access modifiers, interfaces, and more.

If you want to learn more about typescript, I wrote an article about it

Strong Typing and Class Members

class Car {
  public brand: string;
  private speed: number;

  constructor(brand: string) {
    this.brand = brand;
    this.speed = 0;
  }

  accelerate(amount: number): void {
    this.speed += amount;
  }

  get_speed(): number {
    return this.speed;
  }
}

Access Modifiers

  • “public”: accessible from anywhere
  • “private”: accessible only within the class
  • “protected”: accessible within the class and its subclasses
class Vehicle {
  protected wheels: number = 4;
}

class Motorcycle extends Vehicle {
  get_wheels(): number {
    return this.wheels;
  }
}

Interfaces and Abstract Classes

Interfaces define contracts:

interface Flyable {
  fly(): void;
}

class Bird implements Flyable {
  fly(): void {
    console.log('Flying!');
  }
}

Abstract classes define base behavior and required methods:

abstract class Shape {
  abstract area(): number;

  describe(): void {
    console.log('This is a shape.');
  }
}

class Circle extends Shape {
  constructor(public radius: number) {
    super();
  }

  area(): number {
    return Math.PI * this.radius ** 2;
  }
}

Generics for Reusability

If you want to learn more aboyt Generics, here’s an article about it
class Container<T> {
  private items: T[] = [];

  add(item: T) {
    this.items.push(item);
  }

  get_all(): T[] {
    return this.items;
  }
}

const string_container = new Container<string>();
string_container.add("hello");

JavaScript vs TypeScript OOP Summary

FeatureJavaScriptTypeScript
Class SyntaxYesYes
InheritanceYesYes
Access ModifiersLimited (via ”#“)Full (“public”, “private”, “protected”)
Type CheckingNoYes (compile-time)
Interfaces / Abstract ClassesNo (simulated)Yes
GenericsNoYes

Conclusion

OOP helps structure and organize code in scalable and maintainable ways. While JavaScript provides the tools for OOP, TypeScript allows for a true OOP experience, comparable to languages like C# and Java!

You may like: