В минулому ми говорили про те, як JS управляє оголошенням змінних, і на даний момент ми добре знаємо цю частину в JS. Тепер час з'ясувати, що відбувається в JavaScript кожного разу, коли ми запускаємо код. Я вирішив поговорити більше про ці теми, тому що кілька років тому на технічному інтерв'ю (для позиції фронтенд-розробника) мене запитали про виконання JS, і я не мав проблем з поясненням цих функцій, але було трохи дивно почути таке питання. Чому? Як фронтенд-розробник, я завжди стикався з трьома речами на інтерв'ю: "React, hooks та базові питання по JS, такі як функції, типи змінних або TypeScript". Тоді я зрозумів, що просто знати, як оголосити, спроектувати або створити компонент — це найпростіша частина подорожі. Знати, як це працює під капотом, дозволяє стати справжнім розробником програмного забезпечення, а не просто людиною, яка вміє шукати в Google або копіювати/вставляти помилки та питати у ШІ за допомогою. Зрозуміти, що, як і чому.
JS
Модель виконання в JS
JS за своєю природою асинхронний, і саме про це ми будемо говорити сьогодні. Завдяки цьому однопоточному виконанню ми не можемо перейти до наступного рядка коду, поки не завершимо виконання поточного. Ми читаємо та виконуємо код рядок за рядком. Тому почнемо з першої частини Синхронної моделі Javascript:
Перша частина синхронної моделі JS
- Глобальний контекст виконання: Це частина, де JS читає та виконує ваш код з його єдиним потоком.
- Оточення змінних: Ви знаєте, це "частина" пам'яті, де зберігаються наші змінні, в даному випадку це також глобальне оточення* (глобальне в контексті цієї середовища або області).
- Консоль* (Я зазначив консоль тільки в контексті пояснення виведення прикладів, консоль — це об'єкт, що має багато функцій). Якщо хочете дізнатися більше про консоль (можете прочитати тут). Це не є частиною синхронної моделі JS.
Бракує найважливішого, але ми поговоримо про це пізніше. Тепер ми будемо читати та виконувати цей приклад коду так, як JS його обробляє.
Приклад коду
У цьому базовому прикладі ми визначаємо кілька змінних, які представляють суму, яку потрібно заплатити особі, і функцію, яка обчислює загальну суму з урахуванням податків. Перед тим як пояснювати рядок за рядком, ми виконуємо цей код у консолі браузера і дивимося, що відбувається:
Виконання коду у браузері
Нічого не відбувається 🤓, ми просто бачимо "undefined", але не плутайте це. Якщо ви новий розробник, працюючи з JS, ви іноді побачите "undefined" в консолі, це тому що ми оголошуємо змінні, але не обчислюємо нічого, і консоль в браузері завжди оцінює те, що у нас є. В даному випадку ми нічого не повертаємо. Давайте подивимося, що відбувається в самому JS-движку:
Є кілька моментів, на які слід звернути увагу в цьому виконанні:
- Консоль повертає undefined через оцінку введеного запиту.
- Після того як ми зберегли getTotalIva в пам'яті, що наступне, що потрібно прочитати? Ні, це не податки, а person2. Для нас в цей момент і для JS, ми нічого не знаємо про цю змінну, ми зберегли її оголошення від { до }, і перейшли до наступного рядка, person2. 🧐
- Ми зберегли змінні в пам'яті, і я взяв цей момент, щоб пояснити поширену помилку в JS: ми оголошуємо функцію, і вона повністю зберігається як оголошення в пам'яті, що не означає, що вона буде виконана в цей час, поки її не викличемо.
Добре, на цьому приклад здається легким і нічого не пояснює. Отже, давайте викличемо функцію, щоб отримати загальну суму із змінних, які ми зберегли.
Додавши кілька рядків коду, ми повинні побачити це:
Вищезгадане — це оголошення нових змінних, що представляють ... що насправді представляє присвоєння totalperson1? На даний момент ми не турбуємося, ми не знаємо, що повернеться при виклику функції, тому найкращий спосіб пояснити, що це таке — значення totalperson1 це те, що поверне getTotalIva(). Тепер давайте виконаємо цей код в консолі і подивимося, що станеться:
Друге виконання коду, нічого нового
Стек викликів
Час поговорити про відсутню частину цієї моделі, стек викликів. Щоб краще це зрозуміти, давайте подивимося на це зображення:
Привіт! Стек викликів
Ми такі розумні, що знаємо, коли вийти після завершення виконання функції, що була викликана в:
const totalperson = getTotalIva(subtotal)
Після повернення значення, ми знаємо, що воно повинно повернутися до виконання main(), але що щодо JavaScript? йому потрібен механізм, щоб знати, де він знаходиться. І це завдання стеку викликів, вказувати JS, де він виконує код, додаючи новий контекст або видаляючи поточний, щоб повернутися. Цей стек, як і сказано в назві, це "Стек", і говорячи про структури даних (я знаю, що деякі з нас ненавидять цей предмет у школі).
Стек в структурах даних — це лінійний тип структури даних, що слідує принципу LIFO (Last-In-First-Out), дозволяючи операції вставки та видалення з одного кінця структури даних, тобто зверху.
Ми не можемо перейти до наступного рядка, поки не повернемося з нового контексту виконання. Як ви можете бачити, коли ми викликаємо функцію, ми створюємо новий контекст виконання, і тепер ми вперше знаємо про змінні, про які говоримо, і зберігаємо їх (ми видаляємо їх після того, як завершуємо роботу в цій функції), також можна викликати функцію всередині функції, що додасть її зверху стека викликів і створить новий контекст виконання (звісно, є межа, і саме тому інколи через рекурсію ви отримуєте помилку пам'яті). І все! З цим новим компонентом ми завершуємо все, що JavaScript потребує для виконання коду. Подивімося на повне виконання коду в JS:
Повне виконання
Здається, це дуже складно для маленького прикладу коду, але я думаю, ми повинні зрозуміти це від основ до цього рівня, щоб мати змогу говорити про такі теми, як асинхронність або HTTP виклики.
Стек викликів у браузері
Є багато способів побачити, що знаходиться в стеці викликів під час виконання коду, але я думаю, що найзручніше використовувати консоль знову, давайте додамо кілька рядків коду після того, як ми завершимо всі рядки всередині функції, щось типу цього:
Давайте подивимося на стек
Після того, як ми додали нові рядки, ми могли б виконати цей код у консолі браузера і побачити, що станеться:
🙌
Ми бачимо 3 логи відстеження, перші два — з виконання функції 👀, чи бачите ви це як стек? З getTotalIva зверху, а в кінці, після всього коду, ми просто бачимо (анонімну) основну виконувану частину._
Перекладено з: Understanding JS under the hood: Synchronous model code 🤖