Запропонувати правкуПокращити цю статтюДопрацюйте відповідь до «Стрілкові методи проти звичайних». Ваші зміни проходять модерацію перед публікацією.Потрібне підтвердженняКонтентЩо ви змінюєте🇺🇸EN🇺🇦UAПереглядЗаголовок (UA)Коротка відповідь (UA)**Звичайний метод лежить у прототипі класу і отримує `this` у момент виклику, а стрілковий метод, це поле екземпляра, у якому `this` жорстко зафіксовано на об'єкті ще під час його створення.** Через це `setTimeout(u.normal, 500)` втрачає контекст і падає з `TypeError`, а `setTimeout(u.arrow, 500)` працює коректно. Платня за це: стрілковий метод створюється заново для кожного `new`, тому `a1.arrow !== a2.arrow`, він перелічуваний і потрапляє в `Object.keys()`, і його не можна викликати через `super`. Звичайні методи вигідніші за пам'яттю та підтримують наслідування, стрілкові зручніші як обробники подій і колбеки. ```javascript class A { normal() {} arrow = () => {}; } const a1 = new A(), a2 = new A(); console.log(a1.normal === a2.normal); // true, shared on the prototype console.log(a1.arrow === a2.arrow); // false, a new function per instance ``` **Ключове:** звичайний метод, це спільна прототипна функція з динамічним `this`; стрілковий, це власне поле екземпляра з назавжди прив'язаним `this`.Показується над повною відповіддю для швидкого нагадування.Відповідь (UA)Зображення**Звичайний метод класу зберігається в прототипі й отримує `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` на стрілкове поле не вплине.Для рев’юераПримітка для модератора (необов’язково)Бачить лише модератор. Прискорює рев’ю.