Skip to main content

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

Статичні методи в JavaScript - це функції, прикріплені безпосередньо до конструктора класу, а не до екземплярів чи прототипу. Їх викликають через ім'я класу, без new.

Теорія

TL;DR

  • Аналогія: як Math.max() - викликаєш Math, а не екземпляр Math
  • НазваКласу.метод() працює; екземпляр.метод() кидає TypeError
  • Статичні методи зберігаються на конструкторі класу, минаючи ланцюжок прототипів
  • Використовуй, коли логіці не потрібен this екземпляра
  • Правило вибору: немає стану екземпляра - роби статичним

Швидкий приклад

javascript
class Calculator { static add(a, b) { // Прикріплено до Calculator, не до прототипу return a + b; } } console.log(Calculator.add(3, 4)); // 7 const calc = new Calculator(); calc.add(3, 4); // TypeError: calc.add is not a function

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

Ключова різниця

Методи екземплярів зберігаються на Class.prototype і доступні кожному об'єкту, створеному через new. Статичні методи зберігаються безпосередньо на функції-конструкторі. Тому calc.__proto__ не має властивості add, звідси й TypeError. Це не баг і не дивна поведінка - так і задумано специфікацією.

Коли використовувати

  • Математичні або рядкові утиліти без стану: MathUtils.sum(a, b), StringUtils.slugify(str)
  • Фабричні методи (factory methods), що створюють екземпляри з сирих даних: Person.fromString('Alice, 30')
  • Загальні хелпери, які зручно згрупувати під ім'ям класу, а не розкидати як вільні функції
  • Валідатори, що повторно використовуються в різних маршрутах: RouteValidator.validateUserId(id) в Express

Як це працює всередині

V8 зберігає статичні методи як звичайні властивості на функції-конструкторі під час обробки класу, а не на Class.prototype. Коли ти викликаєш MyClass.staticMethod(), рушій виконує прямий пошук властивості на конструкторі й прив'язує this до самого класу. Жодного обходу ланцюжка прототипів, жодного виділення пам'яті під екземпляр.

Поширені помилки

Виклик статичного методу через екземпляр

javascript
const utils = new MathUtils(); utils.sum(1, 2); // TypeError: utils.sum is not a function

Статичні методи знаходяться на класі, а не на Class.prototype. Екземпляр не має до них жодного шляху. Рішення: MathUtils.sum(1, 2).

Очікування, що this всередині статика - це екземпляр

javascript
class Example { static whoami() { console.log(this); } } Example.whoami(); // Виводить клас Example, а не undefined

Всередині статичного методу this - це конструктор класу, а не екземпляр і не undefined. Розробники, що прийшли з Java або Python, часто очікують null. Краще взагалі не покладатися на this у статичних методах і передавати дані параметрами.

Мутабельний спільний стан через статичні властивості

javascript
class Counter { static count = 0; static increment() { this.count++; } } Counter.increment(); Counter.increment(); // Counter.count тепер 2, глобально для всіх

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

Спадкування без super

javascript
class Animal { static getSpecies() { return 'Animal'; } } class Dog extends Animal {} console.log(Dog.getSpecies()); // 'Animal', а не 'Dog'

Статичний метод успадкований від Animal, тому Dog отримує версію батька. Якщо підклас має повертати своє значення, потрібен override або виклик через super:

javascript
class Bird extends Animal { static getSpecies() { return super.getSpecies() + ' (Bird)'; } } console.log(Bird.getSpecies()); // 'Animal (Bird)'

Де зустрічається в реальних проєктах

  • Math.max(), Math.min(), Array.isArray(), Promise.all() - стандартна бібліотека побудована на статиках
  • path.join() у Node.js - чиста утиліта без потреби в екземплярі
  • express.json() - фабрика middleware у стилі статичного методу
  • Date.now() - повертає timestamp без створення об'єкта Date
  • Утиліти Lodash на кшталт _.debounce() слідують тому ж патерну: без стану, без екземпляра

Питання на співбесіді

Q: Чим статичні методи відрізняються від методів прототипу на рівні реалізації?
A: Методи прототипу зберігаються на Class.prototype й доступні через ланцюжок прототипів під час виклику instance.method(). Статичні методи - це властивості безпосередньо на конструкторі. Для статиків обходу ланцюжка немає.

Q: Чи може статичний метод отримати доступ до властивостей екземпляра?
A: Напряму - ні. Всередині статика this - це клас, а не екземпляр. Якщо потрібні дані об'єкта, передай його аргументом: MyClass.process(someInstance).

Q: Що таке this всередині статичного методу?
A: Функція-конструктор класу. Тобто MyClass.someStatic() прив'язує this до MyClass, а не до жодного об'єкта, створеного через new.

Q: Коли краще використати звичайну функцію замість статичного методу?
A: Коли хелпер не пов'язаний з конкретним класом. Статичний метод виправданий, коли групування під іменем класу додає зрозумілості. Якщо ні - вільна функція простіша.

Q: (Senior) Поясни спадкування статичних методів і ключове слово super у статиках.
A: Підкласи успадковують статичні методи батька через ланцюжок прототипів на рівні конструкторів: Dog.__proto__ === Animal. Тому Dog.getSpecies() знаходить Animal.getSpecies. В override можна викликати super.getSpecies(), щоб явно дістатися батьківського статика.

Приклади

Фабричний метод: створення екземпляра з рядка

javascript
class Person { constructor(name, age) { this.name = name; this.age = age; } static fromString(str) { const [name, age] = str.split(', '); return new Person(name, parseInt(age, 10)); } } const alice = Person.fromString('Alice, 30'); console.log(alice.name); // 'Alice' console.log(alice.age); // 30

fromString створює Person, не розкриваючи сигнатуру конструктора. Логіка створення в одному місці, і пізніше можна додати Person.fromJSON(json) чи інші фабрики без зміни точок виклику.

Валідатор вхідних даних у маршруті Express

javascript
class RouteValidator { static validateUserId(id) { const parsed = Number(id); if (!Number.isInteger(parsed) || parsed <= 0) { throw new Error('Invalid user ID'); } return parsed; } } app.get('/users/:id', (req, res) => { try { const userId = RouteValidator.validateUserId(req.params.id); res.json({ userId }); } catch (err) { res.status(400).json({ error: err.message }); } }); // GET /users/123 -> { userId: 123 } // GET /users/abc -> 400 { error: 'Invalid user ID' }

Жодного екземпляра RouteValidator не створюється. Логіка валідації згрупована під зрозумілим ім'ям і повторно використовується в усіх маршрутах, де вона потрібна. Це найпоширеніший реальний патерн для статичних методів у Node.js бекендах.

Коротка відповідь

Для співбесіди
Premium

Коротка відповідь допоможе вам впевнено відповідати на цю тему під час співбесіди.

Дочитали статтю?