Ранее JavaScript предназначался для использования в веб-браузерах, однако ситуация изменилась с развитием Node. Мы знаем, как, где и когда его использовать. Но известно ли, что происходит за этими сценариями?

Даже если вы знаете это, то все равно сможете извлечь полезную информацию из данной статьи.

JavaScript — это высокоуровневый ЯП, а компьютер понимает только единицы и нули. Каким образом компьютер понимает написанный код? В этой статье мы рассмотрим ответ на один единственный вопрос: как работает JavaScript?

Приступим.

Движок JavaScript

Это главный герой, который отвечает за понимание компьютером JS-кода. Движок JavaScript принимает код и преобразует его в машиночитаемый язык. Он выполняет работу переводчика, преобразующего JS-код на понятный компьютеру язык. Как и JS, каждый ЯП также обладает движком, делающий написанный код понятным для компьютера.

У JavaScript есть множество различных движков, доступных для использования. На этой странице Википедии можно найти их список. Они также называются движками ECMAScript (подробнее об этом ниже).

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

Что скрывает движок JavaScript

Движок — это язык, который разбивает код и переводит его. А V8 — это один из самых популярных движков JavaScript, который используется в Chrome и NodeJS и написан на низкоуровневом языке C++. Написать движок может кто угодно, что может привести к путанице.

Для поддержания контроля за этими механизмами был создан стандарт ECMA, который предоставляет характеристики написания движка и всех функций JavaScript. По этой причине в ECMAScript 6, 7, 8 и т. д. реализованы новые функции JavaScript, а движки обновлены для поддержки этих новых функций. Следовательно, необходимо проверять доступность расширенной функции JS в браузерах во время разработки.

Для примера возьмем движок V8, однако основные концепции остаются неизменными во всех движках.

Теперь рассмотрим с более технической точки зрения.

Движок JavaScript V8

Так выглядит движок JS изнутри. Вводимый код проходит через следующие стадии:

  1. Парсер
  2. AST
  3. Интерпретатор выдает байт-код
  4. Профайлер
  5. Компилятор выдает оптимизированный код

Не волнуйтесь, подробности рассмотрим в течение нескольких минут.

Однако для начала разберем важное различие.

Интерпретатор и Компилятор

Есть два способа преобразования кода в машиночитаемый язык. Концепция, о которой мы поговорим, применима не только к JavaScript, но и к большинству ЯП, таких как Python, Java и т. д.

  • Интерпретатор читает код построчно и сразу выполняет его.
  • Компилятор читает весь код, выполняет оптимизации, а затем производит оптимизированный код.

Рассмотрим на примере.

function add(a, b) {
    return a+b
}

for(let i = 0; i < 1000; i++) {
    add(1 + 1)
}

В приведенном выше примере функция add, которая добавляет два числа и возвращает сумму, вызывается 1000 раз.

  1. При передаче этого файла интерпретатору, он читает его построчно и сразу выполняет функцию, пока цикл не закончится. Таким образом, он просто переводит код в то, что компьютер понимает на ходу.
  2. При передаче этого файла компилятору, он читает всю программу, выполняет анализ действий и производит оптимизированный код на языке, который понимает машина. Это как взять X (JS-файл) и создать Y (оптимизированный код, понятный машине). Если использовать интерпретатор для Y (оптимизированный код), результат будет таким же, как при интерпретации X (JS-код).

На изображении выше показано, что байт-код — это просто промежуточный код, который необходимо интерпретировать для обработки компьютером. Как интерпретатор, так и компилятор, преобразуют исходный код в машинный код, единственное отличие состоит в том, как выполняется это преобразование.

  • Интерпретатор построчно преобразует исходный код в эквивалентный машинный код.
  • Компилятор сразу преобразует весь исходный код в машинный код.

Плюсы и минусы интерпретатора и компилятора

  • Преимущество интерпретатора заключается в немедленном выполнении кода без необходимости компиляции, что может быть полезно для запуска JS-файлов в браузере. Однако процесс замедляется при необходимости преобразования большего количества JS-кода. Вспомните маленький фрагмент кода, в котором функция вызывалась 1000 раз. В этом случае вывод остается прежним, даже если функция add была вызвана 1000 раз. Такие ситуации замедляют работу интерпретатора.
  • В таких случаях компилятор может выполнить некоторые оптимизации, заменив цикл одним числом 2 (1 + 1 добавлялось каждый раз), поскольку он остается неизменным для всех 1000 итераций. Окончательный код, который выдает компилятор, оптимизирован и выполняется намного быстрее.

Таким образом, интерпретатор сразу начинает выполнение кода, но не выполняет оптимизацию. Компилятору требуется время для компиляции кода, однако он выдает более оптимизированный код.

Теперь вернемся к основной схеме движка JS.

Итак, зная плюсы и минусы компилятора и интерпретатора, можно использовать преимущества каждого. Здесь и появляется компилятор JIT (Just In Time). Он представляет собой комбинацию интерпретатора и компилятора, и большинство браузеров теперь реализуют эту функцию для повышения эффективности. Движок V8 также использует эту функцию.

Движок V8 JavaScript

В этом процессе:

  1. Парсер идентифицирует, анализирует и классифицирует различные части программы. Например, устанавливает, является ли элемент функцией, переменной и т.д. с помощью ключевых слов JavaScript.
  2. Затем AST (абстрактные синтаксические деревья) создают древовидную структуру на основе классификации парсера. В AST Explorer можно узнать о том, как строится дерево.
  3. Затем дерево передается интерпретатору, который создает байт-код. Как мы узнали ранее, байт-код не является кодом самого низкого уровня, однако его можно интерпретировать. На этой стадии браузер работает с доступным байт-кодом с помощью движка V8, чтобы пользователю не приходилось ждать.
  4. В то же время профайлер ищет оптимизации кода, а затем передает входные данные компилятору. Компилятор выдает оптимизированный код, в то время как байт-код временно используется для рендеринга в браузере. Как только компилятор создает оптимизированный код, временный байт-код полностью заменяется новым оптимизированным кодом.
  5. Таким образом используются лучшие качества интерпретатора и компилятора. Интерпретатор выполняет код, в то время как профайлер ищет оптимизацию, а компилятор создает оптимизированный код. Затем байт-код заменяется оптимизированным кодом, который является кодом более низкого уровня, таким как машинный код.

Это означает, что производительность будет постепенно улучшаться, не блокируя время выполнения.

Примечание по байт-коду

Как и в случае с машинным кодом, не все компьютеры понимают байт-код. Чтобы интерпретировать его на машиночитаемый язык, необходимо промежуточное ПО, такое как виртуальная машина, или движок (например, Javascript V8). По этой причине браузеры могут выполнять этот байт-код из интерпретатора во время вышеупомянутых 5-ти стадий с помощью движков JavaScript.

В результате возникает следующий вопрос:

Является ли JavaScript интерпретируемым языком?

Да, но не совсем. На ранних этапах JavaScript Брендан Айк создал движок JavaScript ‘SpiderMonkey’. У движка был интерпретатор, который говорил браузеру, что нужно делать. Сейчас есть не только интерпретаторы, но и компиляторы, а код не только интерпретируется, но и компилируется для оптимизации. Технически все зависит от реализации.

Читайте также:

Читайте нас в Telegram, VK и Дзен


Перевод статьи Mano lingam: JavaScript: Under the Hood

Предыдущая статьяТоп-10 самых распространенных ошибок в проектах Go. Часть 2
Следующая статьяЭкспоненциальное распределение