Loading...
Loading...
Object-Oriented Programming (OOP) is a programming paradigm based on the concept of "objects" that contain data (properties) and code (methods). OOP helps structure code, making it more understandable, reusable, and easier to maintain.
JavaScript supports OOP but implements it somewhat differently than classical object-oriented languages (Java, C++). Before ES6 (2015), OOP in JavaScript was built on prototypes, and with ES6, class syntax appeared, which is syntactic sugar over prototypal inheritance.
Bundling data and methods that work with that data into a single entity (object or class). Hiding internal implementation and providing a public interface for interaction.
A mechanism that allows creating new classes based on existing ones, inheriting their properties and methods. This promotes code reuse and creation of class hierarchies.
The ability of objects with the same interface to have different implementations. The same method can behave differently depending on the object that calls it.
Highlighting the main, most significant characteristics of an object and ignoring secondary ones. Simplifying complex systems by modeling classes corresponding to the problem domain.
With ES6, class syntax was added to JavaScript, making code more readable and familiar to developers acquainted with classical OOP.
class Person {
constructor(name, age) {
this.name = name;
this.age = age;
}
greet() {
console.log(`Hello, my name is ${this.name}, I'm ${this.age} years old`);
}
celebrateBirthday() {
this.age++;
console.log(`Happy birthday! Now I'm ${this.age} years old`);
}
}
const person = new Person('Alex', 25);
person.greet();
person.celebrateBirthday();
Class Components:
constructor — special method for object initializationthis — reference to current class instanceEncapsulation allows hiding internal implementation details and providing only the necessary interface for interaction.
class BankAccount {
#balance = 0;
constructor(initialBalance) {
this.#balance = initialBalance;
}
deposit(amount) {
if (amount > 0) {
this.#balance += amount;
console.log(`Deposited ${amount}. Balance: ${this.#balance}`);
}
}
withdraw(amount) {
if (amount > 0 && amount <= this.#balance) {
this.#balance -= amount;
console.log(`Withdrawn ${amount}. Balance: ${this.#balance}`);
} else {
console.log('Insufficient funds');
}
}
getBalance() {
return this.#balance;
}
}
const account = new BankAccount(1000);
account.deposit(500);
account.withdraw(200);
console.log(account.getBalance());
Private fields (with # prefix) are inaccessible from outside the class. This provides true encapsulation.
class Rectangle {
constructor(width, height) {
this._width = width;
this._height = height;
}
get area() {
return this._width * this._height;
}
get perimeter() {
return 2 * (this._width + this._height);
}
set width(value) {
if (value > 0) {
this._width = value;
} else {
console.log('Width must be positive');
}
}
set height(value) {
if (value > 0) {
this._height = value;
} else {
console.log('Height must be positive');
}
}
}
const rect = new Rectangle(10, 5);
console.log(rect.area);
rect.width = 15;
console.log(rect.area);
Inheritance allows creating new classes based on existing ones, extending or modifying their functionality.
class Animal {
constructor(name) {
this.name = name;
}
speak() {
console.log(`${this.name} makes a sound`);
}
move() {
console.log(`${this.name} moves`);
}
}
class Dog extends Animal {
constructor(name, breed) {
super(name);
this.breed = breed;
}
speak() {
console.log(`${this.name} barks: Woof-woof!`);
}
fetch() {
console.log(`${this.name} fetches the ball`);
}
}
class Cat extends Animal {
constructor(name, color) {
super(name);
this.color = color;
}
speak() {
console.log(`${this.name} meows: Meow!`);
}
scratch() {
console.log(`${this.name} scratches`);
}
}
const dog = new Dog('Buddy', 'Labrador');
dog.speak();
dog.move();
dog.fetch();
const cat = new Cat('Whiskers', 'Orange');
cat.speak();
cat.move();
cat.scratch();
Key Points:
extends — keyword for inheritancesuper() — calls parent class constructorPolymorphism allows using objects of different classes through a common interface.
class Shape {
constructor(name) {
this.name = name;
}
calculateArea() {
throw new Error('Method calculateArea must be implemented');
}
describe() {
console.log(`This is a ${this.name} with area ${this.calculateArea()}`);
}
}
class Circle extends Shape {
constructor(radius) {
super('Circle');
this.radius = radius;
}
calculateArea() {
return Math.PI * this.radius ** 2;
}
}
class Square extends Shape {
constructor(side) {
super('Square');
this.side = side;
}
calculateArea() {
return this.side ** 2;
}
}
class Triangle extends Shape {
constructor(base, height) {
super('Triangle');
this.base = base;
this.height = height;
}
calculateArea() {
return (this.base * this.height) / 2;
}
}
const shapes = [
new Circle(5),
new Square(4),
new Triangle(6, 3)
];
shapes.forEach(shape => {
shape.describe();
});
Each shape implements the calculateArea() method in its own way, but all can be used through a common interface.
Static methods and properties belong to the class, not its instances.
class MathHelper {
static PI = 3.14159;
static add(a, b) {
return a + b;
}
static multiply(a, b) {
return a * b;
}
static circleArea(radius) {
return this.PI * radius ** 2;
}
}
console.log(MathHelper.add(5, 3));
console.log(MathHelper.circleArea(10));
console.log(MathHelper.PI);
class User {
constructor(name, email, role) {
this.name = name;
this.email = email;
this.role = role;
}
static createAdmin(name, email) {
return new User(name, email, 'admin');
}
static createGuest(name) {
return new User(name, 'guest@example.com', 'guest');
}
static createModerator(name, email) {
return new User(name, email, 'moderator');
}
getInfo() {
return `${this.name} (${this.role}) - ${this.email}`;
}
}
const admin = User.createAdmin('John', 'john@example.com');
const guest = User.createGuest('Guest');
const moderator = User.createModerator('Mary', 'mary@example.com');
console.log(admin.getInfo());
console.log(guest.getInfo());
console.log(moderator.getInfo());
Abstraction allows highlighting the main points and hiding implementation details.
class Database {
connect() {
throw new Error('Method connect must be implemented');
}
disconnect() {
throw new Error('Method disconnect must be implemented');
}
query(sql) {
throw new Error('Method query must be implemented');
}
}
class MySQLDatabase extends Database {
connect() {
console.log('Connecting to MySQL');
}
disconnect() {
console.log('Disconnecting from MySQL');
}
query(sql) {
console.log(`Executing MySQL query: ${sql}`);
return [];
}
}
class PostgreSQLDatabase extends Database {
connect() {
console.log('Connecting to PostgreSQL');
}
disconnect() {
console.log('Disconnecting from PostgreSQL');
}
query(sql) {
console.log(`Executing PostgreSQL query: ${sql}`);
return [];
}
}
function executeQuery(database, sql) {
database.connect();
const result = database.query(sql);
database.disconnect();
return result;
}
const mysql = new MySQLDatabase();
const postgres = new PostgreSQLDatabase();
executeQuery(mysql, 'SELECT * FROM users');
executeQuery(postgres, 'SELECT * FROM products');
Composition is an alternative to inheritance where an object contains other objects instead of inheriting from them.
class Engine {
start() {
console.log('Engine started');
}
stop() {
console.log('Engine stopped');
}
}
class GPS {
getLocation() {
return { lat: 55.7558, lon: 37.6173 };
}
}
class Radio {
play(station) {
console.log(`Playing radio: ${station}`);
}
}
class Car {
constructor() {
this.engine = new Engine();
this.gps = new GPS();
this.radio = new Radio();
}
start() {
this.engine.start();
console.log('Car is ready to go');
}
navigate() {
const location = this.gps.getLocation();
console.log(`Current location: ${location.lat}, ${location.lon}`);
}
listenMusic(station) {
this.radio.play(station);
}
}
const car = new Car();
car.start();
car.navigate();
car.listenMusic('Rock FM');
Composition Advantages:
Before classes appeared, OOP in JavaScript was implemented through constructor functions and prototypes.
function Person(name, age) {
this.name = name;
this.age = age;
}
Person.prototype.greet = function() {
console.log(`Hello, I'm ${this.name}`);
};
Person.prototype.celebrateBirthday = function() {
this.age++;
console.log(`Now I'm ${this.age} years old`);
};
function Developer(name, age, language) {
Person.call(this, name, age);
this.language = language;
}
Developer.prototype = Object.create(Person.prototype);
Developer.prototype.constructor = Developer;
Developer.prototype.code = function() {
console.log(`${this.name} codes in ${this.language}`);
};
const dev = new Developer('Anna', 28, 'JavaScript');
dev.greet();
dev.code();
dev.celebrateBirthday();
Important:
Modern class syntax (ES6+) is syntactic sugar over prototypal inheritance. Under the hood, JavaScript still uses prototypes.
class Singleton {
static instance = null;
constructor() {
if (Singleton.instance) {
return Singleton.instance;
}
Singleton.instance = this;
this.data = [];
}
addData(item) {
this.data.push(item);
}
getData() {
return this.data;
}
}
const instance1 = new Singleton();
instance1.addData('First item');
const instance2 = new Singleton();
instance2.addData('Second item');
console.log(instance1 === instance2);
console.log(instance1.getData());
class Button {
constructor(text) {
this.text = text;
}
render() {
console.log(`Button: ${this.text}`);
}
}
class Input {
constructor(placeholder) {
this.placeholder = placeholder;
}
render() {
console.log(`Input field: ${this.placeholder}`);
}
}
class Select {
constructor(options) {
this.options = options;
}
render() {
console.log(`Dropdown: ${this.options.join(', ')}`);
}
}
class FormElementFactory {
static createElement(type, config) {
switch (type) {
case 'button':
return new Button(config.text);
case 'input':
return new Input(config.placeholder);
case 'select':
return new Select(config.options);
default:
throw new Error('Unknown element type');
}
}
}
const button = FormElementFactory.createElement('button', { text: 'Submit' });
const input = FormElementFactory.createElement('input', { placeholder: 'Enter name' });
const select = FormElementFactory.createElement('select', { options: ['Option 1', 'Option 2'] });
button.render();
input.render();
select.render();
Modularity
Code is divided into independent objects, each responsible for its functionality.
Reusability
Classes and objects can be reused in different parts of the application.
Extensibility
Easy to add new functionality through inheritance or composition.
Maintainability
Changes in one class don't affect other parts of the system (with proper architecture).
Abstraction
Hiding complexity and providing a simple interface for interaction.
Complexity
For small tasks, OOP can be excessive.
Performance
Creating many objects can affect performance.
Deep Hierarchies
Excessive use of inheritance leads to complex and fragile structures.
Over-abstraction
Too many levels of abstraction complicate code understanding.
Suitable for:
Not suitable for:
Summary:
OOP in JavaScript is a powerful paradigm for structuring code. Modern class syntax makes code more readable, but it's important to remember that under the hood JavaScript uses prototypal inheritance. Choose OOP when you need clear structure, encapsulation, and code reuse. For more flexible solutions, consider composition or functional programming.