Skip to main content

Стрілкові методи проти звичайних

Звичайний метод класу зберігається в прототипі й отримує this динамічно, у момент виклику, а стрілковий метод, це поле екземпляра, яке захоплює this під час створення об'єкта й більше його не втрачає. Різниця в синтаксисі мінімальна, але наслідки для пам'яті, наслідування та поведінки в колбеках радикально різні.

Теорія

TL;DR

  • Звичайний метод лежить у Class.prototype і спільний для всіх екземплярів.
  • Стрілковий метод, це власна властивість екземпляра, нова функція на кожен new.
  • У звичайного методу this визначається в момент виклику, у стрілкового він зафіксований назавжди.
  • Стрілковий метод не втрачає контекст у setTimeout чи addEventListener, звичайний втрачає без bind.
  • super працює лише зі звичайними методами; у стрілковому полі його немає.
  • Стрілкові методи перелічувані й потрапляють у Object.keys(), звичайні, ні.

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

javascript
class User { name = "Maria"; normal() { console.log("normal:", this.name); } arrow = () => { console.log("arrow:", this.name); }; } const u = new User(); setTimeout(u.normal, 500); // TypeError: Cannot read properties of undefined setTimeout(u.arrow, 500); // arrow: Maria

normal() втратив контекст, бо був викликаний як звичайна функція. arrow() контекст зберіг, бо стрілкова функція не має власного this, вона «захоплює» this з екземпляра під час ініціалізації поля.

Синтаксис

Звичайний метод:

javascript
class Button { handleClick() { console.log(this.label); } }

Стрілковий метод (метод-поле):

javascript
class Button { handleClick = () => { console.log(this.label); }; }

На перший погляд відмінність лише в = () => {}, але поведінка кардинально різна.

Головна відмінність: поведінка this

Тип методуЯк працює this
Звичайний методthis визначається в момент виклику (динамічно)
Стрілковий методthis жорстко прив'язаний до екземпляра при створенні об'єкта

Саме тому у прикладі вище звичайний метод падає, а стрілковий друкує ім'я.

Де зберігаються методи

Тип методуДе зберігаєтьсяСпільний для всіх екземплярів?
ЗвичайнийУ ClassName.prototypeТак
СтрілковийУ самому екземплярі (на кожному об'єкті)Ні
javascript
class A { normal() {} arrow = () => {}; } const a1 = new A(); const a2 = new A(); console.log(a1.normal === a2.normal); // true, one method on the prototype console.log(a1.arrow === a2.arrow); // false, each instance has its own

Стрілкові методи створюються заново при кожному new A(). Це дає незалежність this, але трохи гірше за пам'яттю та швидкістю створення об'єктів.

Обробники подій і колбеки

Звичайний метод втрачає this, якщо не прив'язати його вручну:

javascript
class Button { constructor() { this.label = "Click me"; document.body.addEventListener("click", this.handleClick); } handleClick() { console.log(this.label); // undefined, the context is lost } }

Рішення 1: прив'язати вручну.

javascript
document.body.addEventListener("click", this.handleClick.bind(this));

Рішення 2 (сучасніше): використати стрілковий метод.

javascript
class Button { label = "Click me"; handleClick = () => { console.log(this.label); // "Click me" }; }

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

Наслідування і перевизначення

Звичайні методи легко перевизначати в нащадках, зокрема через super:

javascript
class A { greet() { console.log("Hello from A"); } } class B extends A { greet() { super.greet(); // works console.log("Hello from B"); } }

А зі стрілковим полем так не вийде:

javascript
class A { greet = () => console.log("Hello from A"); } class B extends A { greet = () => { super.greet(); // error, there is no super method to call }; }

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

Відмінність при ітерації та налагодженні

Звичайні методи неперелічувані, тому їх не видно в Object.keys(). Стрілкові методи, це звичайні властивості екземпляра, тому вони перелічувані:

javascript
class Example { method() {} arrow = () => {}; } const e = new Example(); console.log(Object.keys(e)); // ['arrow']

Це також означає, що стрілкові поля копіюються через spread і Object.assign, а прототипні методи, ні.

Продуктивність

КритерійЗвичайний методСтрілковий метод
Пам'ять1 функція на всіхсвоя функція в кожного
Швидкість створенняШвидшеПовільніше
Контекст thisГубиться без bindЗавжди збережений
Підходить для superТакНі
Підходить для колбеківПотрібен bindПідходить ідеально

Підсумкове порівняння

ОсобливістьЗвичайний методСтрілковий метод
Де зберігаєтьсяClass.prototypeВ екземплярі
Спільний для всіхТакНі
Поведінка thisЗалежить від викликуЗафіксована
Втрата this у колбекахМожливаНемає
Підтримка superТакНі
Бере участь у наслідуванніТакНі
ПродуктивністьКращаТрохи гірша
Використання в React і подіяхНезручноДуже зручно

Висновок: використовуйте звичайні методи, якщо метод логічно спільний для всіх екземплярів і може бути перевизначений; використовуйте стрілкові, якщо важливо зберегти this, наприклад при передачі методу як колбека чи обробника події.

Типові помилки

  • Робити всі методи стрілковими «про всяк випадок». Це множить функції в пам'яті й позбавляє клас нормального наслідування.
  • Очікувати super у стрілковому полі. Його там немає, бо поле не лежить у прототипі.
  • Дивуватися, що Object.keys(instance) показує методи. Показує саме стрілкові поля, бо вони перелічувані власні властивості.
  • Вважати, що звичайний метод «сам» збереже this. Без bind або обгортки-стрілки він втратить контекст у будь-якому колбеку.
  • Плутати порядок ініціалізації. Поля-стрілки створюються при конструюванні екземпляра, тому в базовому класі вони ще недоступні на момент виконання його конструктора в нащадку.
  • Використовувати стрілкові поля там, де метод треба мокати або підмінювати в тестах через прототип. Підміна Class.prototype.method на стрілкове поле не вплине.

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

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

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