Запропонувати правкуПокращити цю статтюДопрацюйте відповідь до «Підняття в JavaScript». Ваші зміни проходять модерацію перед публікацією.Потрібне підтвердженняКонтентЩо ви змінюєте🇺🇸EN🇺🇦UAПереглядЗаголовок (UA)Коротка відповідь (UA)**Hoisting (підняття)** - поведінка JavaScript, коли оголошення реєструються в пам'яті до виконання коду. ```javascript console.log(x); // undefined - var підняте як undefined var x = 5; console.log(y); // ReferenceError - let/const в TDZ let y = 5; greet(); // працює - оголошення функцій підняті повністю function greet() { console.log("Привіт"); } ``` **Ключове:** Оголошення функцій підіймаються з тілом; `var` підіймається як `undefined`; `let`/`const` підіймаються, але недоступні до рядка з оголошенням.Показується над повною відповіддю для швидкого нагадування.Відповідь (UA)Зображення**Hoisting (підняття)** - це поведінка JavaScript, коли оголошення змінних і функцій реєструються в пам'яті під час компіляції, ще до виконання першого рядка коду. ## Теорія ### TL;DR - Уяви перекличку перед уроком: вчитель відмічає всіх присутніх до початку заняття. Оголошення зафіксовані, а значення прийдуть пізніше. - Оголошення функцій через `function` підіймаються повністю. Їх можна викликати до того, як вони з'являться в коді. - `var` підіймається та ініціалізується значенням `undefined`. Звернення до нього до рядка з присвоєнням поверне `undefined`, а не помилку. - `let` і `const` підіймаються, але не ініціалізуються. Звернення до них до оголошення кидає `ReferenceError`. Цей проміжок називається temporal dead zone (TDZ, тимчасова мертва зона). - Правило вибору: `const` за замовчуванням, `let` якщо значення змінюватиметься, `var` тільки в легасі-коді. ### Швидкий приклад ```javascript // Оголошення функції - підняте повністю, це працює console.log(add(2, 3)); // 5 function add(a, b) { return a + b; } // var - піднято як undefined console.log(name); // undefined (не помилка) var name = "Alice"; // let/const - temporal dead zone console.log(age); // ReferenceError: Cannot access 'age' before initialization let age = 25; ``` Рушій вже знає про `add`, `name` і `age` до першого рядка. Але тільки `add` має своє повне значення в той момент. ### Головна різниця JavaScript-рушій обходить твій код двічі. Перший прохід (компіляція): сканує всі оголошення, реєструє їх у пам'яті. Оголошення функцій отримують повний об'єкт функції одразу. Змінні `var` отримують `undefined`. `let` і `const` реєструються, але залишаються неініціалізованими. Саме тому звернення до них до рядка з оголошенням кидає помилку замість тихого повернення `undefined`. TDZ існує навмисно, щоб ловити баги. ### Коли що використовувати - **Оголошення функцій:** Коли треба викликати допоміжну функцію на початку файлу, а визначити її нижче. Поширено в Express-роутах і Node.js-модулях. - **`var`:** Уникай у сучасному коді. Виняток - підтримка IE8 або старіших середовищ. - **`let`/`const`:** За замовчуванням для всього нового коду. `const` запобігає випадковому переприсвоєнню, `let` сигналізує що значення змінюватиметься. ### Два проходи рушія V8 (Chrome/Node.js) і SpiderMonkey (Firefox) обидва роблять два проходи. Перший: сканує область видимості, виділяє пам'ять, встановлює початкові значення. Другий: виконує рядок за рядком. Для `var` і оголошень функцій перший прохід встановлює реальне початкове значення. Для `let`/`const` перший прохід тільки реєструє ім'я. Значення з'являється в другому проході, на точному рядку оголошення. ### Типові помилки **Помилка 1: Очікувати що `var` поважає межі блоку** ```javascript function test() { console.log(y); // undefined, не помилка if (true) { var y = 10; } console.log(y); // 10 - y існує в області функції, не блоку } test(); ``` `var` ігнорує межі блоків. `y` підіймається до функціональної області видимості, тому обидва виклики `console.log` його бачать. Перший повертає `undefined`, другий - `10`. Заміна на `let` змусила б перший рядок кинути `ReferenceError`, що зазвичай і є бажаною поведінкою. **Помилка 2: Рефакторинг оголошення функції в стрілкову** ```javascript // Працює processData(rawData); function processData(data) { return data.map(x => x * 2); } // Після рефакторингу - ламається processData(rawData); // TypeError: processData is not a function const processData = (data) => data.map(x => x * 2); ``` Стрілкові функції та функціональні вирази не підіймаються повністю. Якщо хтось конвертує `function` у `const`, всі виклики вище по файлу одразу ламаються. Класична пастка під час рефакторингу, яка легко проходить локальні тести але ламає білд у іншому порядку файлів. **Помилка 3: Баг із замиканням у циклі** ```javascript // Друкує 3, 3, 3 for (var i = 0; i < 3; i++) { setTimeout(() => console.log(i), 100); } // Друкує 0, 1, 2 for (let i = 0; i < 3; i++) { setTimeout(() => console.log(i), 100); } ``` `var i` підіймається до функціональної області видимості. Всі три колбеки посилаються на одну й ту ж `i`, яка вже дорівнює `3` на момент виконання. `let i` створює окреме прив'язування для кожної ітерації. **Помилка 4: Вважати що функціональний вираз підіймається** ```javascript foo(); // TypeError: foo is not a function var foo = function() { return 42; }; ``` `var foo` підіймається як `undefined`. Тіло функції - ні. Виклик `foo()` до присвоєння - це виклик `undefined()`. ### Де зустрічається у реальному коді - **Express.js:** Обробники роутів як оголошення функцій можуть посилатись на middleware, визначений нижче в тому ж файлі. - **Node.js-модулі:** Публічні функції зверху, приватні хелпери знизу. Працює бо оголошення функцій підняті в межах модуля. - **Jest/Mocha:** `describe()` блоки можуть посилатись на допоміжні функції тестів, визначені після них. - **React-класи:** Методи можуть викликати інші методи класу незалежно від порядку їх визначення в тілі класу. ### Питання на співбесіді **Q:** Що таке temporal dead zone? **A:** TDZ - це проміжок між входом у область видимості та рядком з оголошенням `let`/`const`. В цей час змінна зареєстрована, але не ініціалізована. Звернення до неї кидає `ReferenceError`. Відрізняється від `var`, де змінна одразу ініціалізується як `undefined`. **Q:** Чому `console.log(x)` виводить `undefined`, а не кидає помилку, якщо `var x` оголошено нижче? **A:** Тому що `var x` підіймається і відразу ініціалізується значенням `undefined`. Рушій бачить це як `var x = undefined; console.log(x); x = 5;`. Присвоєння залишається на своєму рядку, але оголошення з початковим значенням переміщується вгору. **Q:** Чи підіймаються функціональні вирази і стрілкові функції? **A:** Ні. Тільки оголошення функцій підіймаються разом із тілом. Функціональні вирази в `var` підіймаються як `undefined`. У `let`/`const` вони потрапляють у TDZ. Виклик до оголошення в обох випадках кидає помилку. **Q (Senior):** `let` і `const` підіймаються, але не ініціалізуються. Навіщо взагалі їх підіймати? **A:** Підняття визначає якій області видимості належить змінна. Якщо `let x` є в блоці, JavaScript знає що `x` належить цьому блоку, а не зовнішній області. Без реєстрації в першому проході рушій пішов би по ланцюгу областей видимості і міг знайти зовнішній `x`, повернувши значення замість помилки. TDZ гарантує що змінна зайнята своїм блоком ще до того, як стане доступною. ## Приклади ### Підняття оголошення функції в модулі ```javascript // Виклик до визначення - працює завдяки hoisting const logger = createLogger("APP"); logger("Server started"); // [APP] Server started function createLogger(prefix) { return function(message) { logMessage(message); }; // logMessage підняте в межах createLogger function logMessage(msg) { console.log(`[${prefix}] ${msg}`); } } ``` `createLogger` підняте до рівня модуля. Всередині нього `logMessage` підняте до області `createLogger`. Повернута функція може безпечно викликати `logMessage`, навіть якщо воно визначено після `return`. ### Баг із замиканням у циклі та виправлення ```javascript // Класична пастка на співбесіді function withVar() { for (var i = 0; i < 3; i++) { setTimeout(() => console.log(i), 0); } } withVar(); // 3, 3, 3 - i спільна для всіх ітерацій // Виправлення через let function withLet() { for (let i = 0; i < 3; i++) { setTimeout(() => console.log(i), 0); } } withLet(); // 0, 1, 2 - кожна ітерація має свою i ``` Одне з найпоширеніших hoisting-питань на JavaScript-співбесідах. `var` підіймає `i` до функціональної області, і всі три замикання захоплюють одну й ту ж змінну. Коли колбеки виконуються, цикл вже завершено і `i` дорівнює `3`. `let` створює нове прив'язування на кожній ітерації.Для рев’юераПримітка для модератора (необов’язково)Бачить лише модератор. Прискорює рев’ю.