Как работает JavaScript: обзор движка, среды выполнения и стека вызовов

Как работает JavaScript: обзор движка, среды выполнения и стека вызовов

JavaScript становится все более популярным, и команды используют его на разных уровнях своего стека - интерфейсные, серверные, гибридные приложения, встроенные устройства и многое другое.

Этот пост предназначен для того, чтобы глубже изучить JavaScript и его работу: зная строительные блоки JavaScript и их взаимодействие друг с другом, вы сможете писать код лучше. В следующих статьях мы также поделимся некоторыми практическими правилами, которые мы используем при разработке SessionStack- легкого JavaScript-приложения, которое должно быть надежным и высокопроизводительным, чтобы оставаться конкурентоспособным.

Как видно из статистики GitHut, JavaScript находится на вершине с точки зрения активных репозиториев и Total Pushes в GitHub. Он не сильно отстает и в других категориях.

1

(Проверьте актуальную статистику языков на GitHub).

Так как проекты все больше зависят от JavaScript, то разработчикам необходимо хорошо понимать внутрненнее устройство и возможности экосистемы для создания хорошего программного обеспечения.

Как оказалось, многие разработчики ежедневно используют JavaScript, но не знают, что происходит под капотом.

Обзор

Почти все уже слышали о V8 Engine как о концепции, и большинство людей знают, что JavaScript является однопоточным или использует колбек очередь.

В этом посте мы подробно рассмотрим все эти концепции и объясним, как на самом деле работает JavaScript. Зная эти детали, вы сможете писать лучшие, не блокирующие приложения, которые должным образом используют предоставленные API.

Если вы новичок в JavaScript, этот пост поможет вам понять, почему JavaScript такой «странный» по сравнению с другими языками.

Если же вы опытный разработчик JavaScript, здесь вы найдете свежую информацию о том, как на самом деле работает JavaScript Runtime, который вы используете каждый день.

Движок JavaScript

Популярным примером движка JavaScript является движок Google V8. Например, V8 используется внутри Chrome и Node.js. Вот очень упрощенное представление о том, как он выглядит:

2

Механизм состоит из двух основных компонентов:

  • Memory Heap - здесь происходит выделение памяти;
  • Call Stack - именно там находятся кадры стека при выполнении кода.

The Runtime

Практически каждый разработчик JavaScript использует браузерные API (например, «setTimeout»). Однако, эти API не предоставляются движком.

Так откуда они?

Оказывается, реальность немного сложнее.

3

Итак, у нас есть движок, но на самом деле гораздо больше - такие вещи, как веб-API, которые предоставляются браузерами: DOM, AJAX, setTimeout и многие другие.

А еще, у нас есть популярный event loop и колбек очередь (callback queue).

Стек вызовов

JavaScript - это однопоточный язык программирования, что означает, что он имеет один стек вызовов. Поэтому он может делать одну вещь за раз.

Стек вызовов - это структура данных, которая в основном записывает, где мы находимся в программе. Если мы переходим в функцию, мы помещаем ее на вершину стека. Если мы возвращаемся из функции, мы выскочим из верхней части стека. Это все, что может делать стек.

Вот пример. Взгляните на следующий код:

function multiply(x, y) {
    return x * y;
}

Когда движок начнет выполнять этот код, стек вызовов будет пуст. После этого шаги будут следующими:

4

Каждая запись в стеке вызовов называется кадром стека (Stack Frame).

И именно так строится стека трейс, когда генерируется исключение - в основном это состояние стека вызовов, в момент когда оно произошло. Взгляните на следующий код:

function foo() {
    throw new Error('SessionStack will help you resolve crashes :)');
}

Если этот код выполняется в Chrome (при условии, что этот код находится в файле с именем foo.js), будет выведен такой стек трейс:

34

« Blowing the stack » - происходит, когда вы достигаете максимального размера стека вызовов. Это может произойти довольно легко, например, при использовании рекурсии без тщательного тестирования кода. Посмотрите на пример:

function foo() {
    foo();
}

Движок начинает выполнение кода с вызова функции «foo». Однако, эта функция, является рекурсивной и начинает вызывать себя без каких-либо условий завершения. Таким образом, на каждом этапе выполнения, одна и та же функция снова и снова добавляется в стек вызовов. Это выглядит примерно так:

5

В какой-то момент, количество вызовов функций в стеке вызовов превышает фактический размер стека вызовов, и браузер решает предпринять действия, выдавая ошибку, которая может выглядеть примерно так:

6

Выполнение кода в одном потоке может быть довольно простым, поскольку вам не нужно иметь дело со сложными сценариями, возникающими в многопоточных средах, например, с дедлоками.

Но работа с одним потоком также довольно ограничена. Поскольку в JavaScript есть один стек вызовов, что же произойдет, когда мы запустим медленные задачи?

Параллелизм и Event Loop

Что происходит, когда в стеке вызовов у вас есть вызовы функций, которые занимают огромное количество времени для обработки? Например, представьте, что вы хотите выполнить сложное преобразование изображений с помощью JavaScript в браузере.

А почему это вообще проблема? Проблема в том, что, несмотря на то, что в стеке вызовов есть функции для выполнения, браузер на самом деле больше ничего не может делать - он блокируется- браузер не может рендерить, не может выполнять любой другой код. Он просто завис. И это проблема для UI, и не единственная. Как только ваш браузер начинает обрабатывать много задач в стеке вызовов, он может перестать отвечать на запросы в течение довольно длительного времени. Большинство браузеров вызывают ошибку, спрашивая вас, хотите ли вы закрыть веб-страницу.

6

Сейчас это не лучший UX, не так ли?

Итак, как же выполнить тяжелый код, не блокируя пользовательский интерфейс и не заставляя браузер виснуть? Решение - асинхронные колбеки.

Об этом более подробно во второй части: «Внутри движка V8 + 5 советов о том, как писать оптимизированный код».

Если вам трудно воспроизводить и понимать проблемы в своих JavaScript приложениях, взгляните на SessionStack. SessionStack записывает все в ваших веб-приложениях: все изменения DOM, взаимодействия с пользователем, исключения JavaScript, трассировки стека, неудачные сетевые запросы и сообщения отладки.

7

Присоединяйтесь к нашим каналам FrontEndDev и Web Stack в Telegram, чтобы не пропустить самое интересное из мира веб-разработки!

Оригинал статьи - How JavaScript works: an overview of the engine, the runtime, and the call stack