Пошук уроків, статей та іншого контенту
Створюватимете статичні поля й методи для логіки, що належить класу, а не окремому екземпляру.
Статичні поля та методи належать самому класу, а не його екземплярам.
Якщо звичайне поле зберігає дані конкретного об’єкта, то статичне поле зберігає дані, спільні для всього класу. Статичний метод викликається через ім’я класу й зазвичай реалізує операцію, для якої не потрібен конкретний екземпляр.
class User {
constructor(name) {
this.name = name;
}
greet() {
return `Привіт, я ${this.name}`;
}
static createGuest() {
return new User("Гість");
}
}
const user = new User("Олена");
console.log(user.greet()); // Привіт, я Олена
console.log(User.createGuest().greet()); // Привіт, я ГістьМетод greet викликається на екземплярі user, а метод createGuest — на самому класі User.
Статичний метод створюють за допомогою ключового слова static:
class MathHelper {
static add(firstNumber, secondNumber) {
return firstNumber + secondNumber;
}
static multiply(firstNumber, secondNumber) {
return firstNumber * secondNumber;
}
}
console.log(MathHelper.add(2, 3)); // 5
console.log(MathHelper.multiply(4, 5)); // 20Виклик через екземпляр призведе до помилки:
const helper = new MathHelper();
// helper.add(2, 3); // TypeError: helper.add is not a functionСтатичний метод не додається до прототипу екземплярів. Він доступний безпосередньо через клас.
Статичний метод доречний, коли:
операція логічно належить класу;
для виконання операції не потрібні дані конкретного екземпляра;
метод є фабрикою для створення екземплярів;
метод виконує перевірку або перетворення даних;
метод містить загальну службову логіку.
Наприклад, метод fromJSON може створити об’єкт класу з JSON-даних:
class Product {
constructor(name, price) {
this.name = name;
this.price = price;
}
static fromJSON(json) {
const data = JSON.parse(json);
return new Product(data.name, data.price);
}
}
const product = Product.fromJSON('{"name":"Клавіатура","price":1500}');
console.log(product.name); // Клавіатура
console.log(product.price); // 1500Статичне поле оголошують у тілі класу з ключовим словом static:
class Configuration {
static environment = "development";
static version = "1.0.0";
}
console.log(Configuration.environment); // development
console.log(Configuration.version); // 1.0.0Статичні поля доступні через клас, але не через його екземпляри:
const configuration = new Configuration();
console.log(configuration.environment); // undefined
console.log(Configuration.environment); // developmentЗначення статичного поля можна змінити:
Configuration.environment = "production";
console.log(Configuration.environment); // productionСтатичні поля часто використовують для:
лічильників;
конфігурації;
констант, пов’язаних із класом;
кешу;
реєстрів створених об’єктів;
значень за замовчуванням.
Статичне поле може зберігати спільний лічильник для всіх екземплярів:
class Order {
static nextId = 1;
constructor(product) {
this.id = Order.nextId;
Order.nextId += 1;
this.product = product;
}
}
const firstOrder = new Order("Ноутбук");
const secondOrder = new Order("Миша");
console.log(firstOrder.id); // 1
console.log(secondOrder.id); // 2
console.log(Order.nextId); // 3Поле nextId одне для всього класу Order. Кожен новий екземпляр використовує його поточне значення та збільшує лічильник.
У статичному методі також можна звертатися до статичних полів:
class Order {
static nextId = 1;
static generateId() {
const id = Order.nextId;
Order.nextId += 1;
return id;
}
constructor(product) {
this.id = Order.generateId();
this.product = product;
}
}
const order = new Order("Монітор");
console.log(order.id); // 1this у статичному методіУ статичному методі this зазвичай посилається на клас, через який метод було викликано:
class Counter {
static value = 0;
static increment() {
this.value += 1;
return this.value;
}
}
console.log(Counter.increment()); // 1
console.log(Counter.increment()); // 2
console.log(Counter.value); // 2Використання this замість назви класу робить метод гнучкішим у разі наслідування:
class Message {
static create(text) {
return new this(text);
}
constructor(text) {
this.text = text;
}
}
class WarningMessage extends Message {}
const message = WarningMessage.create("Небезпечна операція");
console.log(message instanceof WarningMessage); // true
console.log(message.text); // Небезпечна операціяУ цьому прикладі new this(text) створює екземпляр класу WarningMessage, тому що метод викликано через WarningMessage.
Клас може містити обидва типи членів:
class User {
static role = "user";
constructor(name) {
this.name = name;
}
getDisplayName() {
return `${this.name} (${User.role})`;
}
static isValidName(name) {
return typeof name === "string" && name.trim().length >= 2;
}
}
console.log(User.isValidName("Олена")); // true
console.log(User.isValidName("A")); // false
const user = new User("Олена");
console.log(user.getDisplayName()); // Олена (user)Метод isValidName перевіряє будь-яке ім’я, тому йому не потрібен конкретний користувач. Натомість getDisplayName використовує this.name, тому він має бути нестатичним методом екземпляра.
У наступному прикладі клас Task має:
статичний лічильник ідентифікаторів;
статичний реєстр усіх створених задач;
статичний метод пошуку задачі;
статичний метод створення задачі з об’єкта;
нестатичні методи для роботи з конкретною задачею.
class Task {
static nextId = 1;
static all = [];
constructor(title) {
this.id = Task.nextId;
Task.nextId += 1;
this.title = title;
this.completed = false;
Task.all.push(this);
}
complete() {
this.completed = true;
}
getStatus() {
return this.completed ? "виконано" : "активна";
}
static createFromObject(data) {
if (!data || typeof data.title !== "string") {
throw new TypeError("Поле title має бути рядком");
}
return new Task(data.title);
}
static findById(id) {
return Task.all.find((task) => task.id === id);
}
static getCompleted() {
return Task.all.filter((task) => task.completed);
}
}
const firstTask = Task.createFromObject({
title: "Підготувати звіт",
});
const secondTask = Task.createFromObject({
title: "Надіслати лист",
});
secondTask.complete();
console.log(firstTask.id); // 1
console.log(secondTask.id); // 2
console.log(secondTask.getStatus()); // виконано
console.log(Task.findById(1).title); // Підготувати звіт
console.log(Task.getCompleted().length); // 1
console.log(Task.all.length); // 2Task.all належить класу й містить усі створені задачі. Окремий екземпляр має лише власні властивості id, title і completed.
Для реальних застосунків глобальний масив у класі не завжди є найкращим рішенням. Проте такий підхід корисний для демонстрації спільного стану класу.
Статичні методи доступні дочірньому класу через наслідування:
class Vehicle {
static category = "транспорт";
static describe() {
return `Категорія: ${this.category}`;
}
}
class Car extends Vehicle {
static category = "автомобіль";
}
console.log(Car.describe()); // Категорія: автомобільCar не оголошує метод describe, але успадковує його від Vehicle. Усередині методу this посилається на Car, тому використовується Car.category.
Водночас статичне поле не створюється окремо копією в кожному дочірньому класі. Доступ до нього відбувається через ланцюжок наслідування класів:
class Parent {
static value = 10;
}
class Child extends Parent {}
console.log(Child.value); // 10
Child.value = 20;
console.log(Child.value); // 20
console.log(Parent.value); // 10Після присвоєння Child.value = 20 у дочірнього класу створюється власна властивість value. Значення Parent.value не змінюється.
Статичний блок дає змогу виконати код один раз під час створення класу:
class AppConfig {
static settings = {};
static {
AppConfig.settings.environment = "development";
AppConfig.settings.debug = true;
}
}
console.log(AppConfig.settings);
// { environment: "development", debug: true }Статичний блок може використовуватися для складнішої ініціалізації кількох статичних полів. Він виконується під час обчислення оголошення класу, а не під час створення екземпляра.
У простих випадках краще використовувати безпосереднє оголошення статичного поля:
class AppConfig {
static environment = "development";
}Статичний блок доречний, коли початкове значення потрібно обчислити або підготувати кілька пов’язаних значень.
Для приватного статичного члена використовують символ #:
class ApiClient {
static #token = "secret-token";
static getToken() {
return ApiClient.#token;
}
}
console.log(ApiClient.getToken()); // secret-tokenПриватне поле недоступне за межами класу:
// console.log(ApiClient.#token); // SyntaxErrorПриватні статичні члени корисні для внутрішнього стану класу, який не повинен бути частиною його публічного API:
class IdGenerator {
static #lastId = 0;
static next() {
IdGenerator.#lastId += 1;
return IdGenerator.#lastId;
}
}
console.log(IdGenerator.next()); // 1
console.log(IdGenerator.next()); // 2Під час проєктування класу корисно поставити запитання: чи потрібен методу конкретний об’єкт?
class Temperature {
constructor(celsius) {
this.celsius = celsius;
}
toFahrenheit() {
return this.celsius * 9 / 5 + 32;
}
static fromFahrenheit(fahrenheit) {
const celsius = (fahrenheit - 32) * 5 / 9;
return new Temperature(celsius);
}
}
const temperature = Temperature.fromFahrenheit(68);
console.log(temperature.celsius); // 20
console.log(temperature.toFahrenheit()); // 68fromFahrenheit створює новий об’єкт і не працює з уже існуючим екземпляром, тому він статичний.
toFahrenheit перетворює значення конкретного об’єкта, тому він нестатичний.
Такий статичний метод часто називають фабричним методом.
class Logger {
static info(message) {
console.log(message);
}
}
const logger = new Logger();
// logger.info("Повідомлення"); // TypeError
Logger.info("Повідомлення");Статичний метод викликають через клас.
this для поля екземпляра у статичному методіclass User {
constructor(name) {
this.name = name;
}
static getName() {
return this.name;
}
}
const user = new User("Олена");
console.log(user.name); // Олена
console.log(User.getName()); // UserУ статичному методі this — це клас, а не екземпляр. Тому this.name тут не означає ім’я користувача.
Неправильно:
class Item {
constructor() {
this.count = 0;
this.count += 1;
}
}У кожного екземпляра буде власне поле count, яке щоразу починається з нуля.
Правильно:
class Item {
static count = 0;
constructor() {
Item.count += 1;
}
}
new Item();
new Item();
console.log(Item.count); // 2Статичний масив або об’єкт доступний усьому коду, який має доступ до класу. Якщо змінювати його без контролю, це може ускладнити відстеження змін.
Для складного стану варто:
приховувати його приватним статичним полем;
змінювати його через методи;
чітко визначати, хто відповідає за його оновлення;
розглянути окремий клас або модуль для керування станом.
Іноді в статичному методі пишуть назву базового класу замість this:
class Document {
static create(text) {
return new Document(text);
}
constructor(text) {
this.text = text;
}
}
class Report extends Document {}
const report = Report.create("Звіт");
console.log(report instanceof Document); // true
console.log(report instanceof Report); // falseЯкщо фабричний метод має підтримувати наслідування, краще використовувати new this(...):
class Document {
static create(text) {
return new this(text);
}
constructor(text) {
this.text = text;
}
}
class Report extends Document {}
const report = Report.create("Звіт");
console.log(report instanceof Report); // trueСтатичні поля та методи належать класу, а не його екземплярам.
Статичний член викликають або читають через ім’я класу.
Статичний метод не може напряму використовувати поля конкретного екземпляра.
Статичні поля зручно використовувати для лічильників, конфігурації, реєстрів і спільного стану.
Фабричні методи на кшталт fromJSON або create часто є статичними.
Статичні методи можуть успадковуватися дочірніми класами.
У статичному методі this зазвичай посилається на клас, через який метод викликали.
Приватні статичні поля та методи позначають символом #.
Спільний статичний стан потрібно використовувати обережно, щоб не створити приховані залежності.