ООП в JavaScript

Огляд різних парадигм в JavaScript

Об’єктно-орієнтоване програмування (ООП) – це парадигма програмування, яка базується на концепціях об’єктів та класів. В ООП програми організовані навколо об’єктів, які включають дані та методи для їх обробки. В цьому розділі ми розглянемо основні концепції ООП, різницю між ООП, процедурним та функціональним програмуванням та призначення ООП в JavaScript.

Процедурне програмування

Процедурне програмування – це парадигма, в якій програма організована навколо послідовно виконуваних процедур або функцій. Дані та функції відокремлені, і програміст має керувати потоком виконання програми.

// Процедурний підхід
function calculateSum(a, b) {
  return a + b;
}

const result = calculateSum(5, 3);
console.log(result); // 8

Функціональне програмування

Функціональне програмування – це парадигма, в якій функції розглядаються як об’єкти першого класу, тобто можуть бути передані як аргументи і повернуті із функцій. Функції працюють з імутабельними даними, не змінюючи їх.

// Функціональний підхід
const calculateSum = (a, b) => a + b;

console.log(calculateSum(5, 3)); // 8

Ось ще один, але більш наглядний приклад фунціонального підходу

// Функція мапування: створює новий масив, де кожен елемент - потрійне значення початкового масиву
const triple = arr => arr.map(num => num * 3);

// Функція фільтрації: створює новий масив, включаючи тільки парні числа
const filterEven = arr => arr.filter(num => num % 2 === 0);

// Функція що обчислює суму всіх елементів у масиві
const calculateSum = arr => arr.reduce((acc, num) => acc + num, 0);

// Вхідний масив
const numbers = [1, 2, 3, 4, 5];

// Використання функцій для обробки масиву
const tripledNumbers = triple(numbers);
const evenNumbers = filterEven(tripledNumbers);
const sum = calculateSum(evenNumbers);

console.log(tripledNumbers); // [3, 6, 9, 12, 15]
console.log(evenNumbers);    // [6, 12]
console.log(sum);            // 18

// Але часто ви можете побачити такий код
// Він виконує ті самі дії

console.log(calculateSum(filterEven(triple(numbers))))

Об’єктно-орієнтоване програмування (ООП)

У ООП програма організована навколо об’єктів, які містять дані та методи для їх обробки. Об’єкти визначаються класами, які визначають структуру та поведінку об’єктів.

// ООП підхід
class Calculator {
  constructor(a, b) {
    this.a = a;
    this.b = b;
  }

  calculateSum() {
    return this.a + this.b;
  }
}

const calculator = new Calculator(5, 3);
const result = calculator.calculateSum();
console.log(result); // 8

Відмінності між парадигмами

  1. Структура програми: У процедурному підході програма базується на послідовності функцій. У функціональному підході – на функціях та даних. У ООП – на об’єктах та класах.
  2. Управління станом: У процедурному підході стан контролюється глобальними змінними. У функціональному – через імутабельні дані. У ООП – через методи об’єктів.
  3. Переваги: Функціональне програмування сприяє розділенню стану та обчислень, ООП – управлінню комплексними структурами даних.

ООП в JavaScript

JavaScript підтримує ООП за допомогою прототипної моделі. Об’єкти можуть мати властивості та методи, а їх структура може бути визначена через прототипи або класи (за допомогою сучасного синтаксису ES6).

Сутності ООП в JS

Сутності ООП є основоположними поняттями об’єктно-орієнтованого програмування (ООП). Вони допомагають організовувати програмний код у більш логічні та структуровані блоки, що полегшує розробку та підтримку програм. Основними сутностями ООП є клас, екземпляр та інтерфейс.

Клас

Клас є шаблоном або описом для створення об’єктів. Він визначає структуру та поведінку об’єкта. Класи можуть мати властивості (часто називають полями) та методи (функції, пов’язані з класом). Клас визначає, які дані може містити об’єкт, та які дії можуть бути виконані над об’єктом.

class Person {
  constructor(name, age) {
    this.name = name;
    this.age = age;
  }

  greet() {
    console.log(`Hello, my name is ${this.name} and I'm ${this.age} years old.`);
  }
}

Екземпляр (Instance)

Екземпляр є конкретним представником класу. Він створюється на основі шаблону, яким є клас. Кожен екземпляр має свої власні значення властивостей, але спадковує методи та структуру від класу.

const person1 = new Person("Alice", 25);
const person2 = new Person("Bob", 30);

person1.greet(); // Hello, my name is Alice and I'm 25 years old.
person2.greet(); // Hello, my name is Bob and I'm 30 years old.

Інтерфейс

Інтерфейс визначає контракт, який має бути виконаний класом або об’єктом. Це список методів, які клас або об’єкт має реалізувати. Інтерфейс вказує, які операції можуть бути виконані з об’єктом, не розглядаючи його внутрішньої реалізації.

У JavaScript немає звичних інтерфейсів, оскільки це динамічно типізована мова. Натомість для JS характерна “качина типізація”.

Duck typing є концепцією в програмуванні, що використовувується в динамічних програмних мовах, як JavaScript, Python, і Ruby. Це метод визначення типу об’єкта, що базується на його поведінці або методах і властивостях які він має.

Назва “duck typing” походить від виразу “If it looks like a duck, swims like a duck, and quacks like a duck, then it probably is a duck.” У програмуванні це означає що якщо якийсь об’єкт має властивості певного типу і поводиться як певний тип то він може бути представлений як цей тип, незважаючи на його справжній клас або тип.

class Calculator {
  add(a, b) {
    return a + b;
  }

  subtract(a, b) {
    return a - b;
  }
}

const calc = new Calculator();
console.log(calc.add(5, 3)); // 8
console.log(calc.subtract(10, 4)); // 6

У цьому прикладі інтерфейс Calculator визначає методи add та subtract, які об’єкт цього класу повинен реалізувати.

Конструктор

Конструктор – це спеціальний метод, який викликається при створенні нового екземпляра класу. Він використовується для ініціалізації властивостей об’єкта. У JavaScript конструктор називається constructor.

class Person {
  constructor(name, age) {
    this.name = name;
    this.age = age;
  }
}

const person1 = new Person("Alice", 25);
const person2 = new Person("Bob", 30);

Властивості

Властивості – це дані, які зберігаються в об’єкті. Вони описують характеристики об’єкта. Властивості можуть бути публічними (доступними ззовні) або приватними (доступними лише в межах класу).

class Rectangle {
  constructor(width, height) {
    this.width = width;
    this.height = height;
  }
}

const rect = new Rectangle(10, 5);
console.log(rect.width); // 10
console.log(rect.height); // 5

Методи

Методи – це функції, пов’язані з класом, які можуть виконувати дії над об’єктом чи повертати результати. Методи можуть також звертатися до властивостей об’єкта.

Публічні властивості та методи

Публічні властивості та методи доступні для зовнішнього коду, і можна звертатися до них безпосередньо через об’єкт або екземпляр класу.

class Student {
  constructor(name) {
    this.name = name; // Публічна властивість
  }

  // Публічний метод
  introduce() {
    console.log(`Hello, my name is ${this.name}`);
  }
}

const student = new Student("Alice");
console.log(student.name); // Alice
student.introduce(); // Hello, my name is Alice

Приватні властивості та методи

Приватні властивості та методи підтримуються у стандарті ECMAScript 2019 за допомогою синтаксису #.

class Counter {
  #count = 0; // Приватна властивість

  // Приватний метод
  #increment() {
    this.#count++;
  }

  getCount() {
    return this.#count;
  }
}

const counter = new Counter();
console.log(counter.getCount()); // 0
counter.#count = 5; // Помилка! Приватна властивість не доступна ззовні
counter.#increment(); // Помилка! Приватний метод не доступний ззовні

Приватні властивості та методи є недоступними ззовні класу і навіть з підкласів.

Захищені властивості та методи

Ми побачили що є такі модифікатори:

  • публічні методи і властивості – доступні в самому класі, ззовні та в дочірніх класах.
  • приватні методи і властивості – доступні тільки в самому класі та НЕ доступні ззовні та в дочірніх класах.

Але що робити коли ми хочемо мати методи та властивості які водночас були б приватними але були доступні з дочірніх класів? Тут на допомогу приходять “захищені” методи та властивості.

Захищені властивості та методи використовують практику позначення змінних чи методів підкресленням (_) перед іменем, щоб показати, що вони не повинні безпосередньо використовуватися ззовні класу.

Це не підтримується на рівні мови програмування, але існує домовленість про те, що до таких властивостей і методів не можна звертатися ззовні. Більшість програмістів дотримуються цього. Тобто по факту такі властивості та методи є публічними, але згідно конвенції ви не повинні звертатися до них поза класом або поза дочірнім класом.

class BankAccount {
  constructor(balance) {
    this._balance = balance; // Захищена властивість
  }

  // Захищений метод
  _getBalance() {
    return this._balance;
  }
}

Часто ви можете почути що такі властивості і методи називають приватними. І це не є помилкою, оскільки ця конвенція виникла ще до появи приватних полів і методів в мові програмування. Тому тоді вони виконували і функцію приватних методів і полів також.

Геттери і Сеттери

Ключові слова get і set в JavaScript використовуються для визначення спеціальних методів доступу до властивостей об’єкта. Ці методи дозволяють контролювати доступ до властивостей, виконувати додаткові дії при зчитуванні або записі значень властивостей. Це є частиною механізму інкапсуляції, який дозволяє приховувати деталі реалізації властивостей від зовнішнього світу.

Ключове слово get:

get використовується для визначення методу, який буде викликаний при зчитуванні властивості. Це дозволяє виконати певні дії перед поверненням значення властивості. Зазвичай геттери створюють якраз для приватних та захищених полів, щоб відкрити можливість отримання значення, але в той же час все ще неможливо змінити ці значення ззовні.

В більшості випадків геттери і сеттери мають однакові імена і відображають ім’я захищеної/приватної властивості але без спеціальних символів (#,_) на початку імені.

Приклад:

class Circle {
  constructor(radius) {
    this._radius = radius;
  }

  get radius() {
    return this._radius;
  }

  get area() {
    return Math.PI * this._radius ** 2;
  }
}

const circle = new Circle(5);
console.log(circle.radius); // 5
console.log(circle.area); // 78.53981633974483

Ключове слово set:

set використовується для визначення методу, який буде викликаний при записі значення властивості. Це дозволяє виконати певні дії перед зміною значення властивості.

Приклад:

class Temperature {
  constructor(celsius) {
    this._celsius = celsius;
  }

  get celsius() {
    return this._celsius;
  }

  set celsius(value) {
    if (value < -273.15) {
      console.log("Temperature below absolute zero is not possible.");
      return;
    }
    this._celsius = value;
  }

  get fahrenheit() {
    return this._celsius * 9 / 5 + 32;
  }
}

const temp = new Temperature(25);
console.log(temp.celsius); // 25
console.log(temp.fahrenheit); // 77

temp.celsius = -300; // Temperature below absolute zero is not possible.
temp.celsius = 32;
console.log(temp.celsius); // 32
console.log(temp.fahrenheit); // 89.6

Використання get і set дозволяє контролювати зчитування і запис значень властивостей, а також виконувати додаткові дії при цьому. Це може бути корисно, наприклад, для перевірки допустимості значень або автоматичного обчислення деяких властивостей на основі інших.

Статичні методи та властивості

Статичні методи та властивості є частинами класів в JavaScript, але вони належать самому класу, а не екземплярам цього класу. Це означає, що ви можете викликати статичні методи та звертатися до статичних властивостей без створення об’єкта класу.

Статичні методи та властивості корисні для функціональності, яка не пов’язана з конкретним екземпляром об’єкта, а затребує доступу до самого класу.

Статичні методи

Для створення статичних методів використовується ключове слово static. Вони викликаються через сам клас, а не через екземпляр класу.

class MathHelper {
  static square(number) {
    return number * number;
  }
}

console.log(MathHelper.square(5)); // 25

Статичні методи можуть бути корисними для виконання загальних обчислень або додаткової функціональності, яка не потребує створення екземплярів класу.

Статичні властивості

Статичні властивості – це змінні або константи, які належать самому класу, а не його екземплярам. Вони оголошуються поза конструктором класу.

class Configuration {
  static defaultLanguage = "en";
}

console.log(Configuration.defaultLanguage); // "en"

Статичні властивості можуть використовуватися для зберігання констант або значень, що відносяться до самого класу, а не до окремого екземпляру.

Використання статичних методів та властивостей

Статичні методи та властивості можуть полегшити організацію коду та виконання загальних завдань, специфічних для класу.

Дуже часто статичні методи і властивості можна зустріти у “допоміжних” класах.

Наприклад:

class Logger {
  static log(message) {
    console.log(`[LOG] ${message}`);
  }
}

Logger.log("Hello, world!"); // [LOG] Hello, world!
export class TimeHelper {
	static async delay(ms){
		return new Promise((resolve) => setTimeout(resolve, ms));
	}
}

Наслідування

Прототипна модель наслідування є ключовим аспектом об’єктно-орієнтованого програмування в JavaScript. Вона відрізняється від класичної моделі наслідування, яку можна знайти в інших мовах програмування. Прототипи дозволяють об’єктам використовувати та вспадковувати властивості та методи інших об’єктів. Давайте розглянемо прототипну модель наслідування більш детально.

Прототипи та наслідування

Кожен об’єкт в JavaScript має прототип (властивість __proto__), який є об’єктом також. Прототип визначає властивості та методи, які можуть бути успадковані іншими об’єктами. Коли властивість чи метод не знаходяться у поточному об’єкті, JavaScript автоматично перевіряє прототип для їх пошуку.

// Створення об'єкта "прототипу"
const animalPrototype = {
  speak() {
    console.log(`${this.name} says ${this.sound}`);
  }
};

// Створення об'єкта, який успадковує прототип
const dog = {
  name: "Dog",
  sound: "Woof"
};
dog.__proto__ = animalPrototype;

// Виклик методу від успадкованого прототипу
dog.speak(); // Dog says Woof

Як це працює під капотом

Коли ми викликаємо метод чи отримуємо властивість об’єкта, JavaScript перш за все перевіряє, чи є така властивість у самому об’єкті. Якщо не знайдено, JavaScript перевіряє прототип. Цей процес продовжується вгору по ланцюжку прототипів, доки властивість чи метод не буде знайдено або досягнуто верхнього рівня прототипу (Object.prototype).

У прикладі вище, коли ми викликаємо dog.speak(), спершу перевіряється об’єкт dog і метод speak не знаходиться в ньому. Тоді JavaScript переходить до прототипу animalPrototype і знаходить метод speak. Таким чином, метод speak успадковується від прототипу.

Оголошення та наслідування класів у ES6

ES6 вводить новий синтаксис для оголошення класів, але насправді вони використовують ту ж саму прототипну модель наслідування. Розглянемо це на прикладі:

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

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

class Dog extends Animal {
  constructor(name) {
    super(name, "Woof");
  }
}

const dog = new Dog("Dog");
dog.speak(); // Dog says Woof

У цьому прикладі Dog успадковує властивості та методи від Animal, використовуючи ключове слово extends. Ключове слово super використовується для виклику конструктора батьківського класу.

Конструктор дочірнього класу

При створенні дочірнього класу необхідно використовувати ключове слово extends для вказання батьківського класу, від якого буде спадати дочірній клас. Дочірній клас може мати свій власний конструктор або використовувати конструктор батьківського класу за допомогою ключового слова super.

super це посилання на конструктор батьківського класу

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

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

class Dog extends Animal {
  constructor(name, breed) {
    super(name);
    this.breed = breed;
  }

  speak() {
    console.log(`${this.name} barks.`);
  }
}

const dog = new Dog("Buddy", "Golden Retriever");
console.log(dog.name); // Buddy
console.log(dog.breed); // Golden Retriever
dog.speak(); // Buddy barks.

Методи дочірнього класу

Дочірній клас може мати власні методи, які мають такі самі імена як методи батьківського класу. Це називається поліморфізмом, де методи з однаковим ім’ям в різних класах можуть виконувати різні дії.

В той же час будь-який клас, незалежно від того він наслідується від іншого класу чи ні може мати власні методи і властивості.

class Shape {
	constructor(color) {
		this.color = color;
	}

	draw() {
		console.log(`Drawing a shape with ${this.color} color.`);
	}
}

class Circle extends Shape {
	constructor(color, radius) {
		super(color);
		this.radius = radius;
	}

	printInfo() {
		console.log(`INFO: Radius : ${this.radius}, color: ${this.color}`);
	}

	draw() {
		console.log(`Drawing a circle with ${this.color} color and radius ${this.radius}.`);
	}
}

const circle = new Circle('blue', 5);
circle.draw(); // Drawing a circle with blue color and radius 5.
circle.printInfo(); // INFO: Radius : 5, color: blue

Дочірній клас може використовувати методи батьківського класу, якщо вони не є приватними.

class Square extends Shape {
  constructor(color, sideLength) {
    super(color);
    this.sideLength = sideLength;
  }
}

const square = new Square("red", 4);
square.draw(); // Drawing a shape with red color.

При використанні ієрархії класів дочірні класи можуть успадковувати та розширювати функціональність батьківського класу, що спрощує організацію коду та забезпечує більшу реюзабельність.

Значення this

  1. В методі об’єкта: this вказує на об’єкт, до якого відноситься метод.
  2. В методі класу: this вказує на поточний об’єкт-екземпляр класу.

Поліморфізм. Абстракція. Інкапсуляція

Поліморфізм

Цей принцип дозволяє використовувати один і той самий інтерфейс для об’єктів різних класів. Це дозволяє замінювати один об’єкт іншим, що дозволяє більш гнучко взаємодіяти з різними типами даних.

Приклад:

class Animal {
  makeSound() {
    console.log("Some generic sound");
  }
}

class Dog extends Animal {
  makeSound() {
    console.log("Woof woof!");
  }
}

class Cat extends Animal {
  makeSound() {
    console.log("Meow!");
  }
}

function animalSound(animal) {
  animal.makeSound();
}

const dog = new Dog();
const cat = new Cat();

animalSound(dog); // Woof woof!
animalSound(cat); // Meow!

Абстракція

Цей принцип дозволяє створювати спрощений інтерфейс для взаємодії з об’єктами, приховуючи деталі реалізації. Абстракція допомагає зосередитися на важливих аспектах та спростити взаємодію з об’єктами.

Приклад:

class Car {
  constructor(make, model) {
    this.make = make;
    this.model = model;
  }

  drive() {
    console.log(`${this.make} ${this.model} is driving.`);
  }
}

const myCar = new Car("Toyota", "Camry");
myCar.drive(); // Toyota Camry is driving.

Інкапсуляція

Цей принцип дозволяє приховувати деталі реалізації внутрішнього стану об’єкта від зовнішнього світу. Це дозволяє контролювати доступ до внутрішніх деталей і забезпечує абстракцію.

Приклад:

class BankAccount {
  #balance = 0;

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

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

const account = new BankAccount();
account.deposit(100);
console.log(account.getBalance()); // 100

Детально про прототипне успадкування

Вбудовані класи

Вбудовані обʼєкти(вбудовані класи) в JavaScript являють собою визначені обʼєкти, що надаються мовою для роботи з різними типами даних. Ці класи включають Array, Object, String, Number, Boolean, Date, та інші. Кожен з них надає набір методів та властивостей, специфічних для відповідного типу даних, що робить їх корисними інструментами для обробки даних у різних сценаріях.

За допомогою кожного з цих вбудованих класів ми з вами явним чи неявним чином створюємо їх екземпляри(інстанси). Наприклад, коли ми створюємо масив, [1, 2, 3], то це альтернативний варіант синтаксису new Array. Кожного разу, коли ми створюємо новий інстанс фактично будь-якого типу, тому що типи null і undefined не мають відповідного класу, то ми утворюємо звʼязок між інстансом та класом, а точніше з прототипом.

Що таке прототип?

Прототип — це механізм, який дозволяє об’єктам спадковувати властивості та методи від інших об’єктів. Іншими словами, кожен об’єкт в JavaScript може мати посилання на інший об’єкт, який вважається його «прототипом». Це дозволяє об’єктам спільно використовувати код та функціональність інших об’єктів, що робить код більш ефективним та об’єктно-орієнтованим.

Кожен з цих вбудованих класів — це функція, яка містить в собі властивість prototype, який в свою чергу є обʼєктом і містить у собі всі вбудовані методи. А кожен обʼєкт містить властивість __proto__ яка містить посилання на свій prototype і таким чином утворюється ланцюжок прототипів.

Ланцюжок прототипів

За допомогою ланцюжока прототипів ми отримаємо доступ до властивостей та методів які зберігаються у прототипі. Наприклад наш масив [1, 2, 3] має метод map з Array.prototype. Але у механізми читання та запису дещо відрізняються.

Механізм [[Get]]

Механізм [[Get]] є способом отримання значення властивості з об’єктів, враховуючи прототипне успадкування. Коли JavaScript виконує операцію отримання значення властивості типу obj.property, він перевіряє, чи є така властивість в самому об’єкті obj. Якщо властивість існує, значення властивості повертається. Якщо властивість не існує в самому об’єкті, JavaScript йде ланцюжком прототипів, починаючи з поточного об’єкта obj, і шукає цю властивість в прототипах. Як тільки властивість знайдена в одному з об’єктів прототипу — її значення буде повернено у якості результату. Якщо ж такої властивості не буде знайдено, то ми отримаємо undefined.

Механізм [[Set]]

Механізм [[Set]] у ланцюжку прототипів є способом зчитування значення властивості в об’єктах. Навідміну від [[Get]], якщо необхідної властивості у цільовому обʼєкті не буде знайдено, то вона буде автоматично створена, чи оновлена, якщо вона вже існує.

Shadowing

Property shadowing (затінення властивості) — це ситуація, коли об’єкт має властивість з тим самим ім’ям, що й властивість об’єкту-прототипа. Внаслідок цього властивість внутрішнього об’єкта «затіняє» властивість прототипу, роблячи її недоступною для прямого доступу.

const user = {
  toString() {
    return '[object User]';
  }
};

Переваги прототипного успадкування

Для чого все це потрібно і які надає нам переваги?

  • Простота та гнучкість: Прототипне успадкування надає простий та гнучкий спосіб створення та організації об’єктів. Можете створювати об’єкти, додавати методи та властивості до прототипів, і успадковувати їх у міру необхідності.
  • Ефективне використання пам’яті: У прототипному наслідуванні методи та властивості поділяються між усіма об’єктами, що успадковують від одного і того ж прототипу. Це дозволяє заощаджувати пам’ять, оскільки методи та властивості не дублюються для кожного об’єкта.
  • Відкритість для розширення: Прототипне успадкування дозволяє додавати нові методи та властивості до об’єктів без зміни вихідного коду. Це забезпечує відкритість для розширення та підтримку змінних вимог.

Способи перевірки прототипу

hasOwnProperty

Метод hasOwnProperty — це вбудований метод об’єктів, який дозволяє перевірити, чи містить об’єкт власну (не успадковану) властивість із заданим ім’ям. Цей метод приймає рядок, що представляє ім’я властивості, і повертає true, якщо об’єкт містить власну властивість із цим ім’ям, і false якщо ні.

const person = {
  name: 'John',
  age: 42
};

console.log(person.hasOwnProperty('age')); // true
console.log(person.hasOwnProperty('toString')); // false

Метод hasOwnProperty корисний для виключення успадкованих властивостей при переборі властивостей об’єкта, використовуючи цикл for…in

const user = {
	login() {
	  return true;
  }
}

const person = {
  name: 'John',
  age: 42,
  __proto__: user
};

for (const item in person) {
  if (person.hasOwnProperty(item)) {
    console.log('this is a property of the person - ', `${item} : `, person[item]);
  } else {
    console.log('this is the prototype property - ', `${item} :`, person[item]);
  }
}

Object.hasOwn

Метод Object.hasOwn() повертає значення true якщо вказана властивість є властивістю об’єкта (не успадкованною), навіть якщо значення властивості дорівнює null або undefined. Метод повертає false якщо властивість є успадковано або властивість взагалі не було оголошено. На відміну від оператора in цей метод не перевіряє зазначену властивість в ланцюжку прототипів об’єкта.

const user = {
    login() {
        return true;
    }
}

const person = {
  name: 'John',
  age: 42,
  __proto__: user
};

console.log(Object.hasOwn(person, 'name')); // true
console.log(Object.hasOwn(person, 'login')); // false

Його рекомендується використовувати замість Object.hasOwnProperty() оскільки він працює для об’єктів, у яких немає прототипу та з об’єктами, які перевизначають успадкований hasOwnProperty(). Хоча ці проблеми можна обійти, викликавши Object.prototype.hasOwnProperty(), але Object.hasOwn() більш інтуїтивно зрозумілий.

Object.getOwnPropertyNames

Метод Object.getOwnPropertyNames() — повертає масив з іменами всіх власних (не успадкованих) перерахованих властивостей об’єкта. Цей метод приймає об’єкт як аргумент і повертає масив рядків, які мають імена властивостей об’єкта.

const user = {
    login() {
        return true;
    }
}

const person = {
  name: 'John',
  age: 42,
  __proto__: user
};

console.log(Object.getOwnPropertyNames(person)); // ['name', 'age']

Object.getOwnPropertySymbols

Метод Object.getOwnPropertySymbols() — який дозволяє отримати масив всіх власних символьних (не успадкованих) властивостей об’єкта. Символьні властивості є унікальними та не перераховуються, що означає, що їх не буде видно при звичайному переборі властивостей об’єкта з використанням циклу for…in та Object.getOwnPropertyNames().

const symbolA = Symbol('Prototype symbol');
const symbolB = Symbol('Own symbol');

const user = {
	[symbolA]: 'Prototype symbol',
	login() {
		return true;
	}
}

const person = {
	[symbolB]: 'Own symbol',
	name: 'John',
	age: 42,
	__proto__: user
};

console.log(Object.getOwnPropertyNames(user)); // ['login']
console.log(Object.getOwnPropertyNames(person)); // ['name', 'age']

console.log(Object.getOwnPropertySymbols(user)); // [Symbol(Prototype symbol)]
console.log(Object.getOwnPropertySymbols(person)); // [Symbol(Own symbol)]

Object.getPrototypeOf

Метод Object.getPrototypeOf() повертає прототип (тобто внутрішню властивість [[Prototype]]) зазначеного об’єкта.

const user = {
	login() {
		return true;
	}
}

const person = {
	name: 'John',
	age: 42,
	__proto__: user
};

console.log(Object.getPrototypeOf(person)); // user

Як раніше зазначалося що властивість __proto__, можна використовувати для отримання об’єкта, але його використання не рекомендується через можливі проблеми сумісності та безпеки. Для рішення цих проблем яка раз і був запроваджений метод Object.getPrototypeOf() .

isPrototypeOf

Метод isPrototypeOf() — це вбудований метод, який дозволяє перевірити, чи є об’єкт прототипом для іншого об’єкта. Цей метод повертає true, якщо об’єкт є прототипом іншого об’єкта, і false в іншому випадку.

Синтаксис методу:

const user = {
	login() {
		return true;
	}
}

const person = {
	name: 'John',
	age: 42,
	__proto__: user
};

console.log(user.isPrototypeOf(person)); // true

Підсумок

JavaScript є мультипарадигменною мовою програмування тобто підтримує кілька парадигм програмування, зокрема процедурну, об’єктно-орієнтовану, функціональну та імперативну. Кожна з них має свої особливості та способи підходу до створення програм.

Поняття ООП в JavaScript

  1. Клас: Шаблон, за яким створюються об’єкти.
  2. Екземпляр: Об’єкт, створений на основі певного класу.
  3. Інтерфейс: Спосіб взаємодії з об’єктом через методи та властивості.
  4. Конструктор: Спеціальний метод для створення та ініціалізації об’єкту.
  5. Властивості та Методи: Дані та функції, що належать об’єктам класу.
  6. Модифікатори доступу: Надають варіативність контролю доступу до властивостей та методів (public/private/protected).
  7. Статичні методи та властивості: Методи та властивості, які відносяться до самого класу, а не до його екземплярів.
  8. Геттери і Сеттери: Функції для отримання та встановлення значень властивостей.

Принципи ООП в JS

  1. Наслідування: Механізм, який дозволяє класам успадковувати властивості та методи інших класів.
  2. Поліморфізм: Здатність об’єктів виконувати однакові дії, використовуючи різні реалізації.
  3. Інкапсуляція: Обмеження доступу до певних компонентів об’єкта та приховання деталей реалізації.
  4. Абстракція: Спрощення складності шляхом визначення загальних методів та властивостей для об’єктів.