Класи були введені в EcmaScript 2015 (ES6), для полегшення розробки програмного забезпечення на основі концепцій ООП (об’єктно-орієнтоване програмування). Клас є шаблоном для створення об’єктів, котрий інкапсулює всі дані та функціональність об’єкта в одному місці.

  1. Оголошення класу в JavaScript
  2. constructor – ініціалізація екземпляра класу
  3. Поля
  4. Поля екземплярів класу
  5. Статичні поля класу
  6. Методи
  7. Методи екземплярів класу
  8. Статичні методи класу
  9. Getters та Setters
  10. Спадкування (extends) класів в JavaScript
  11. Батьківський конструктор та використання super()
  12. Виклик методів батьківського класу з використанням super
  13. Перевірка типу об’єкта instanceof
  14. Класи та прототипи в JavaScript
  15. Висновок

Оголошення класу в JavaScript

Для оголошення класу використовується ключове слово class, після котрого слідує ім’я класу:

class User {}

Класи використовуються для створення екземплярів (об’єктів, що містять конкретні дані та логіку описані в класі). Екземпляри створюються за допомогою оператора new:

let user = new User();

Ключове слово new створює новий об’єкт (екземпляр класу), та викликає метод конструктора цього об’єкта.

constructor – ініціалізація екземпляра класу

constructor(param1, param2, …) – це спеціальний метод всередині класу, котрий використовується для ініціалізації екземпляра класу (встановлюються початкові значення полів і здійснюється його налаштування).

Приклад конструктора що встановлює початкове значення поля name:

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

let user = new User('Lucas Bradley');
console.log(`user name: ${user.name}`);

// OUTPUT: user name: Lucas Bradley

Аргументи, котрі використовуються під час створення екземпляра класу, передаються параметрам його конструктора (в поточному прикладі це значення для поля name). Ключове слово this всередині конструктора, вказує на поточний створюваний екземпляр.

Якщо не визначити власний конструктор, буде автоматично створено конструктор за замовчуванням, котрий являє собою порожню функцію constructor, яка не впливає на екземпляр.

В класі може бути тільки один метод з ім’ям constructor. Якщо клас містить більше ніж один constructor, буде згенеровано помилку:

SyntaxError: A class may only have one constructor

Поля

Поля – це змінні, котрі містять інформацію. Поля в класі JavaScript можна розділити на дві групи:

  • поля екземплярів класу
  • поля самого класу (статичні)

Поля також мають два рівні доступу:

  • відкриті (публічні): поля доступні всередині класу та в екземплярах
  • приватні: поля доступні тільки всередині класу

Поля екземплярів класу

Відкриті поля

Повернемось до класу User з прикладу вище:

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

Вираз this.name = name, створює поле екземпляра name, та присвоює йому початкове значення. Доступ до цього поля можна отримати наступним чином:

let user = new User('Lucas Bradley');
console.log(user.name);

// OUTPUT: Lucas Bradley

В цьому прикладі name – відкрите поле, до нього є доступ за межами класу User.

Окрім неявного створення полів екземпляру всередині конструктора, є можливість явно визначити поля всередині класу. Додатково, там же можна присвоювати полям початкові значення:

class User {
  name;
  role = 'user';

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

const user = new User('Lucas Bradley');
console.log(`name: ${user.name}, role: ${user.role}`);

// OUTPUT: name: Lucas Bradley, role: user

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

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

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

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

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

Для того щоб зробити поле приватним, перед його назвою слід поставити символ # (наприклад #fieldName). Під час звернення до такого поля, завжди має використовуватись зазначений префікс.

Додаю приватне поле viewName в клас User:

class User {
  name;
  role = 'user';
  #viewName;

  constructor(name) {
    this.name = name;
    this.#viewName = name.toUpperCase();
  }

  getViewName() {
    return this.#viewName;
  }
}

let user = new User('Lucas Bradley');

console.log(`User name: ${user.getViewName()}`); // OUTPUT: User name: LUCAS BRADLEY
user.#viewName = 'Lowri Ramirez'; // Помилка

#viewName – приватне поле. Доступ до нього можна отримати тільки всередині класу User, що дозволяє зробити метод getViewName().

Однак, при спробі отримати доступ до #viewName за межами класу User (рядок 19 в прикладі вище), буде згенеровано помилку:

SyntaxError: Private field '#viewName' must be declared in an enclosing class

Статичні поля класу

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

Для створення статичного поля, використовується ключове слово static, перед назвою поля (наприклад: static myStaticField). Розглянемо приклад:

class User {
  static ROLE_ADMIN = 'admin';
  static ROLE_USER = 'user';

  name;
  role;

  constructor(name, role) {
    this.name = name;
    this.role = role;
  }
}

let admin = new User('Hugo', User.ROLE_ADMIN);
console.log(`name: ${admin.name}, role: ${admin.role}`);

// OUTPUT: name: Hugo, role: admin

В прикладі вище, клас User містить поле role, для визначення ролі користувача. Статичні поля ROLE_ADMIN і ROLE_USER – константи що містять значення ролей користувачів.

Доступ до статистичного поля класу, отримується без участі екземпляра класу. Для доступу до статичних полів, слід використовувати назву класу в поєднанні з назвою поля, наприклад: User.ROLE_ADMIN та User.ROLE_USER.

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

Для оголошення приватного статичного поля, потрібно перед назвою поля поставити префікс # (наприклад: static #myPrivateStaticFiled).

Припустимо, що нам потрібно обмежити кількість екземплярів класу. Для приховування інформації про кількість екземплярів, можна створити приватні статичні поля:

class User {
  static #instances = 0;
  static #MAX_INSTANCES = 2;

  name;

  constructor(name) {
    User.#instances++;
    if (User.#instances > User.#MAX_INSTANCES) {
      throw new Error('Unable to create an instance of the class User');
    }
    this.name = name;
  }
}

new User('Rafael');
new User('Kelly');
new User('Kelsie'); // Error: Unable to create an instance of the class User

Статичне приватне поле User.#MAX_INSTANCES визначає допустиму кількість екземплярів, а User.#instances – кількість створених екземплярів.

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

Методи

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

JavaScript підтримує методи екземплярів класу та статичні методи.

Методи екземплярів класу

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

Приклад методу getUpperName() класу User, що повертає ім’я користувача у верхньому регістрі:

class User {
  name;

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

  getUpperName() {
    return this.name.toUpperCase();
  }
}

let user = new User('Christopher');
console.log(`name: ${user.getUpperName()}`);

// OUTPUT: name: CHRISTOPHER

В методі класу, так само як і в конструкторі, ключове слово this вказує на поточний екземпляр. this можна використовувати для отримання даних екземпляра (наприклад this.fieldName), або для виклику його методів (наприклад this.methodName()).

Методи також можуть бути приватними. Щоб зробити метод приватним, слід використовувати префікс # перед ім’ям методу.

Приклад приватного методу:

class Person {
  #firstName;
  #lastName;

  constructor(firstName, lastName) {
    this.#firstName = firstName;
    this.#lastName = lastName;
  }

  getFullName(short = true) {
    return short ? this.#shortFormat() : this.#fullFormat();
  }

  #shortFormat() {
    return `${this.#firstName.charAt(0)}. ${this.#lastName}`;
  }

  #fullFormat() {
    return `${this.#firstName}, ${this.#lastName}`;
  }
}

let person = new Person('Валентин', 'Рибалко');
console.log(person.getFullName());

// OUTPUT: В. Рибалко

#shortFormat() та #fullFormat() – приватні методи, котрі викликаються всередині іншого метода класу getFullName(short = true), за допомогою синтаксису this.#.

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

SyntaxError: Private field '#shortFormat' must be declared in an enclosing class

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

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

Для створення статичного методу, використовується ключове слово static перед назвою методу (наприклад static staticMethodName()).

При використання статичних методів, слід пам’ятати два важливих правила:

  • статичний метод має доступ до статичних полів
  • статичний метод не має доступу до полів екземпляра

Приклад статичного методу, котрий перевіряє що користувач із зазначеним ім’ям вже існує:

class User {
  name;
  static #names = [];

  static isNameExist(name) {
    return User.#names.includes(name);
  }

  constructor(name) {
    this.name = name;
    User.#names.push(name);
  }
}

let user = new User('Bruce');

console.log(User.isNameExist('Bruce')); //OUTPUT: true
console.log(User.isNameExist('Cindy')); //OUTPUT: false

isNameExist(name) – статичний метод, котрий використовує приватне статичне поле #names, для визначення використаних імен.

Статичні методи також можуть бути приватними (приклад визначення: static #privateStaticMethodName()). Такі методи можуть викликатися лише всередині класу.

Getters та Setters

Getters та Setters – це accessors (або обчислювані властивості), спеціальні методи котрі імітують поля, та дають змогу читати і записувати в них дані:

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

Приклад:

class User {
  #nameValue;

  get name() {
    return this.#nameValue;
  }

  set name(name) {
    if (name === '') {
      throw new Error('The user name cannot be empty');
    }
    this.#nameValue = name;
  }
}

let user = new User();
user.name = 'Cassius'; // викликається setter
console.log(user.name); // викликається getter, що повертає Cassius

user.name = ''; // Error: The user name cannot be empty

В прикладі вище, getter та setter використовуються для обгортання поля name класу User, що дає змогу встановити контроль над встановленням і отриманням його значення.

Спадкування (extends) класів в JavaScript

Класи в JavaScript підтримують успадкування за допомогою ключового слова extends. Клас потомок, успадковує від батьківського конструктор, поля та методи.

Приклад дочірнього класу Employee, котрий розширює батьківський клас Person:

class Person {
  name;

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

  getName() {
    return this.name;
  }
}

class Employee extends Person {
  skills = [];
}

let employee = new Employee('Orlando');
console.log(employee.getName()); // OUTPUT: Orlando

Клас Employee успадковує від Person конструктор, поле name та метод getName(). У самому Employee визначається нове поле skills.

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

Батьківський конструктор та використання super()

Для виклику конструктора батьківського класу в дочірньому, слід використовувати спеціальну функцію super(), котра доступна в конструкторі дочірнього класу.

В прикладі нижче, конструктор класу Employee викликає батьківський конструктор, а також робить ініціалізацію власного поля job:

class Person {
  name;

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

  getName() {
    return this.name;
  }
}

class Employee extends Person {
  job;

  constructor(name, job) {
    super(name);
    this.job = job;
  }
}

let employee = new Employee('Lexi Willis', 'manager');

console.log(employee.getName()); // OUTPUT: Lexi Willis
console.log(employee.job); // OUTPUT: manager

super(name) (рядок17) що знаходиться у дочірньому класі Employee, викликає конструктор батьківського класу Person.

Слід звернути увагу, що виклик super() повинен відбуватись перед використанням ключового слова this. Виклик super() “прив’язує” батьківський конструктор до екземпляра. Якщо зробити наступним чином, працювати не буде:

class Employee extends Person {
  job;

  constructor(name, job) {
    this.job = job;
    super(name);
  }
}

Подібний код згенерує помилку:

ReferenceError: Must call super constructor in derived class before accessing 'this' or returning from derived constructor

Виклик методів батьківського класу з використанням super

Для отримання доступу до методу батьківського класу, всередині методу дочірнього класу, слід використовувати super:

class Person {
  name;

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

  getName() {
    return this.name;
  }
}

class Employee extends Person {
  job;

  constructor(name, job) {
    super(name);
    this.job = job;
  }

  getName() {
    let name = super.getName();
    if (name === '') {
      return 'Lexi Willis';
    }
    return name;
  }
}

let employee = new Employee('', 'manager');

console.log(employee.getName()); // OUTPUT: Lexi Willis

Метод getName() дочірнього класу Employee, викликає метод getName() (рядок 22), батьківського класу Person, обгортаючи його власною логікою. Цей механізм називається перевизначенням методу.

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

Перевірка типу об’єкта instanceof

Вираз object instanceof class визначає, чи є об’єкт екземпляром зазначеного класу. Приклад:

class Person {}

class Employee extends Person {}

let employee = new Employee();

console.log(employee instanceof Employee); // true
console.log(employee instanceof Person); // true

Оператор instanceof поліморфічний, тобто він досліджує весь ланцюжок спадкування класів. Таким чином, для екземпляра на котрий посилається змінна employee, повертається результат true, при порівнянні з Employee та Person.

Якщо потрібно визначити конкретний клас екземпляра, слід використовувати властивість constructor:

class Person {}

class Employee extends Person {}

let employee = new Employee();

console.log(employee.constructor === Employee); // true
console.log(employee.constructor === Person); // false

Класи та прототипи в JavaScript

Cинтаксис класів – це абстракція над прототипним успадкуванням в JavaScript. При використанні класів немає потріби звертатись до прототипів. Однак, класи є лише надбудовою над прототипним успадкуванням. Будь-який клас – це функція, що створює екземпляр під час виклику його конструктора.

Розглянемо два приклади, котрі є ідентичними.

Використання класу:

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

  getName() {
    return this.name;
  }
}

let person = new Person('Raphael Drake');

console.log(person.getName()); // Raphael Drake
console.log(person instanceof Person); // true

Використання прототипу:

function Person(name) {
  this.name = name;
}

Person.prototype.getName = function () {
  return this.name;
};

let person = new Person('Raphael Drake');

console.log(person.getName()); // Raphael Drake
console.log(person instanceof Person); // true

Висновок

Клас – це синтаксичний цукор над прототипним успадкуванням JavaScript, з додатковими покращеннями. Для оголошення нового класу використовується ключове слово class. Класи в сучасному JavaScript використовуються скрізь, для ініціалізації екземплярів за допомогою конструктора, визначення їхніх полів та методів.


Мітки: