Запропонувати правкуПокращити цю статтюДопрацюйте відповідь до «Відмінності між стрілковою функцією, оголошенням функції та виразом функції». Ваші зміни проходять модерацію перед публікацією.Потрібне підтвердженняКонтентЩо ви змінюєте🇺🇸EN🇺🇦UAПереглядЗаголовок (UA)Коротка відповідь (UA)**Arrow function, function declaration та function expression** - три способи оголосити функцію в JavaScript з різною поведінкою hoisting (підняття) і прив'язкою `this`. ```javascript function decl() {} // піднімається, динамічний this const expr = function() {}; // не піднімається, динамічний this const arrow = () => {}; // не піднімається, лексичний this, не конструктор ``` **Ключове:** Arrow function успадковує `this` з місця оголошення і не може бути конструктором.Показується над повною відповіддю для швидкого нагадування.Відповідь (UA)Зображення**Function declaration**, **function expression** та **arrow function** - три способи визначити функцію в JavaScript, кожен з яких по-різному поводиться з hoisting (підняттям), прив'язкою `this` та підтримкою конструктора. ## Теорія ### TL;DR - Function declaration повністю піднімається - можна викликати до рядка де вона написана - Function expression і arrow function у TDZ до рядка присвоєння - виклик раніше кидає ReferenceError - Arrow function не має власного `this` - бере його лексично з батьківської області видимості - Arrow function не можна використовувати з `new` - немає внутрішнього методу `[[Construct]]` - Правило вибору: declaration для утиліт верхнього рівня, arrow для колбеків і обробників, expression для IIFEs ### Швидкий приклад ```javascript // Declaration: піднімається, працює до оголошення sayHi(); // 'Hi!' - без помилки function sayHi() { console.log('Hi!'); } // Expression: TDZ при const - виклик до рядка кидає помилку // sayHiExpr(); // ReferenceError const sayHiExpr = function() { console.log('Hi from expression!'); }; // Arrow: лексичний this - бере з зовнішньої області const obj = { name: 'World', greet: () => console.log('Hi ' + this.name) // undefined, не 'World' }; obj.greet(); // 'Hi undefined' ``` Тільки declaration працює до свого рядка. `this.name` в arrow повертає `undefined`, бо стрілка захопила `this` із зовнішньої (модульної) області видимості, а не з `obj`. ### Головна різниця Найважливіше - це **hoisting**. Function declaration парситься до виконання будь-якого коду: весь тіл потрапляє у VariableEnvironment виконавчого контексту ще до першого рядка. Expression і arrow піднімають лише змінну: `undefined` при `var`, або TDZ при `const`/`let`. Плюс до цього, arrow взагалі не створює власних `this` та `arguments` - обидва беруться з батьківського лексичного середовища через scope chain. ### Коли що використовувати - Утиліта верхнього рівня, потрібна з будь-якого місця файлу - function declaration - Колбек, метод масиву або обробник події - arrow function (короткий синтаксис, правильний `this`) - IIFE або повернення з функції вищого порядку де важлива назва у stack trace - function expression - Конструктор що викликається через `new` - declaration або expression, але не arrow - Обробник події у class-компоненті - arrow (автоматично прив'язує `this` до instance) ### Таблиця порівняння | Характеристика | Function Declaration | Function Expression | Arrow Function | | --- | --- | --- | --- | | **Синтаксис** | `function name() {}` | `const name = function() {}` | `const name = () => {}` | | **Hoisting** | Повний: весь тіл | Тільки змінна (TDZ при `const`/`let`) | Тільки змінна (TDZ при `const`/`let`) | | **Прив'язка `this`** | Динамічна (залежить від виклику) | Динамічна (залежить від виклику) | Лексична (зовнішня область видимості) | | **Об'єкт `arguments`** | Так | Так | Ні - використовуй rest (`...args`) | | **Конструктор (`new`)** | Так | Так | Ні - кидає TypeError | | **`super` у класах** | Так | Так | Ні | | **Коли використовувати** | Підняті утиліти, module exports | IIFEs, функції вищого порядку | Колбеки, `map`/`filter`, React-обробники | ### Як це обробляє рушій V8 парсить function declaration на пре-виконавчій фазі: повний об'єкт функції потрапляє у VariableEnvironment виконавчого контексту ще до першого рядка коду. Expression і arrow парсяться як літерали під час виконання - прив'язуються до змінної лише коли інтерпретатор доходить до цього рядка. Arrow взагалі не створює прив'язок для `this` та `arguments`: при зверненні до `this` всередині стрілки рушій іде вгору по scope chain до батьківського LexicalEnvironment. ### Типові помилки **Виклик arrow до її оголошення:** ```javascript greet(); // ReferenceError: Cannot access 'greet' before initialization const greet = () => console.log('hi'); ``` `const` тримає `greet` у TDZ. Перенеси виклик нижче або використай function declaration. **Очікуваний динамічний `this` у методі-стрілці:** ```javascript const obj = { name: 'Alice', say: () => console.log(this.name) // undefined, не 'Alice' }; obj.say(); ``` Arrow взяла `this` з модульної області, а не з `obj`. Використовуй звичайний метод: `say() { console.log(this.name); }`. **`arguments` у стрілковій функції:** ```javascript const sum = () => console.log(arguments); // ReferenceError або зовнішній arguments sum(1, 2, 3); ``` Arrow не має об'єкту `arguments`. Замість нього - rest-параметри: `const sum = (...args) => args.reduce((a, b) => a + b, 0)`. **Arrow як конструктор:** ```javascript const User = () => ({ name: 'Bob' }); new User(); // TypeError: User is not a constructor ``` Немає `[[Construct]]` - немає `new`. Використовуй `function User() {}` або `class User {}`. Ця помилка трапляється в code review частіше ніж здається - зазвичай коли хтось переписує метод класу в стрілку і забуває оновити місця виклику. **`super` у arrow-полі класу:** ```javascript class Child extends Parent { // arrow = () => super.method(); // SyntaxError method() { super.method(); } // правильно } ``` ### Де зустрічається в реальних проєктах - React - arrow class fields (`handleSubmit = () => {}`) щоб прив'язати `this` до instance без `.bind(this)` у конструкторі - Express.js - arrow у route-колбеках (`app.use((req, res, next) => {})`) для передбачуваного `this` - Redux - arrow-селектори (`const getUsers = state => state.users`) - Node.js streams - function declaration для парсерів що викликаються з кількох місць у коді - Lodash - named function expression у `_.curry(function add(a, b) {})` щоб рекурсивні виклики і stack traces залишалися читабельними ### Питання на співбесіді **Q:** Що відбудеться якщо оголосити function expression через `var` замість `const`? **A:** Змінна підійметься як `undefined`. Виклик до рядка присвоєння дасть `TypeError: sayHi is not a function` - не ReferenceError, бо змінна існує, але містить `undefined`. **Q:** Чому у arrow function немає `prototype`? **A:** Специфікація не додає `prototype` до arrow, бо їх не можна використовувати з `[[Construct]]`. Немає сенсу виділяти властивість яка ніколи не буде використана. **Q:** Чи змінюється `this` у function declaration у strict mode? **A:** Так. У strict mode `this` всередині функції без явного контексту виклику стає `undefined`, а не глобальний об'єкт. Arrow це не стосується, бо вона не створює власного `this`. **Q:** Чи можна дати arrow function ім'я для кращих stack traces? **A:** Коли присвоюєш стрілку іменованій `const`, рушій виводить ім'я змінної як ім'я функції - воно видно у stack trace. Але написати іменовану стрілку напряму так само як `function log() {}` не вийде. **Q:** (Senior) Яка різниця між IIFE-стрілкою з `const` і function expression з `var` з точки зору TDZ? **A:** `const f = (() => {})()` виконується нормально на цьому рядку - TDZ блокує лише звернення до `f` до нього. З `var f = (function(){})()` - var підіймається як `undefined`, тому звернення до `f` до рядка дає `undefined`, а виклик `f()` кидає `TypeError: f is not a function`. Різні помилки з різними повідомленнями - і це важливо при дебагу. ## Приклади ### Hoisting на практиці ```javascript // Declaration доступна відразу console.log(add(2, 3)); // 5 function add(a, b) { return a + b; } // Expression з const: TDZ до рядка присвоєння // console.log(multiply(2, 3)); // ReferenceError const multiply = function(a, b) { return a * b; }; console.log(multiply(2, 3)); // 6 ``` Виклик `add` на рядку 2 працює бо declaration була піднята разом із тілом. Закоментований виклик `multiply` кинув би ReferenceError - `const` тримає змінну в TDZ до рядка присвоєння. ### Arrow function і `this` у React class-компоненті ```javascript class Button extends React.Component { constructor(props) { super(props); this.state = { count: 0 }; // Expression: потребує .bind(this) щоб отримати правильний контекст this.handleClickExpr = function() { this.setState({ count: this.state.count + 1 }); }.bind(this); } // Arrow: захоплює this з тіла класу, bind не потрібен handleClickArrow = () => { this.setState({ count: this.state.count + 1 }); }; render() { return <button onClick={this.handleClickArrow}>{this.state.count}</button>; } } ``` Expression-варіант потребує `.bind(this)`, інакше при виклику з браузера `this` буде `undefined`. Arrow захоплює `this` з тіла класу автоматично. Саме тому arrow class fields стали стандартним патерном у pre-hooks React. ### Arrow як конструктор - де падає у продакшені ```javascript function Decl() { this.type = 'declaration'; } const Expr = function() { this.type = 'expression'; }; const Arrow = () => {}; new Decl(); // { type: 'declaration' } new Expr(); // { type: 'expression' } new Arrow(); // TypeError: Arrow is not a constructor // Реальний патерн що ламається у рантаймі function createInstance(Ctor) { return new Ctor(); } createInstance(Decl); // ок createInstance(Arrow); // TypeError у продакшені ``` `new Arrow()` падає бо arrow не має методу `[[Construct]]`. Небезпека в тому що помилки компіляції немає. Якщо передати arrow у фабрику що очікує конструктор - креш вилізе в рантаймі, не під час розробки.Для рев’юераПримітка для модератора (необов’язково)Бачить лише модератор. Прискорює рев’ю.