Post Content

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:
- Encapsulation – Group related data and behavior (methods) into objects.
- Abstraction – Hide complex implementation details behind simple interfaces.
- Inheritance – Allow new classes to inherit properties and methods from existing ones.
- 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 itclass 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
Feature | JavaScript | TypeScript |
---|---|---|
Class Syntax | Yes | Yes |
Inheritance | Yes | Yes |
Access Modifiers | Limited (via ”#“) | Full (“public”, “private”, “protected”) |
Type Checking | No | Yes (compile-time) |
Interfaces / Abstract Classes | No (simulated) | Yes |
Generics | No | Yes |
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:
Building Reusable PostgreSQL Helper Functions in Deno with deno-postgres
Boilerplate code is life.
Interface vs Type – A Comprehensive Guide
Life is full of choices, choose right.