Давно обсуждаемый вопрос для кода, написанного на JS: это интерпретируемый скрипт или скомпилированная программа? Большинство мнений, кажется, что JS на интерпретируемом (сценарном) языке. Но так ли это на самом деле или правда сложнее?

Быстрое обновление (согласно Википедии)

Интерпретатор – это компьютерная программа, которая непосредственно выполняет инструкции, написанные на языке программирования или языке сценариев, без необходимости их предварительной компиляции в программу на машинном языке. Он переводит одно Заявление за раз.

Компилятор – это компьютерное программное обеспечение, которое преобразует компьютерный код, написанный на одном языке программирования (исходном языке, таком как JavaScript и т. д.), в другой язык программирования (целевой язык, например, машинный код).

Давайте вернемся к основному делу.

Согласно документации в MDN, JavaScript описывается так:
JavaScript (JS) — это легковесный, интерпретируемый или компилируемый точно в срок язык программирования с первоклассными функциями. Хотя он наиболее известен как язык сценариев для веб-страниц, многие небраузерные среды также используют его, например Node.js, Apache CouchDB и Adobe Acrobat. JavaScript — основанный на прототипах, многопарадигменный, однопоточный, динамический язык, поддерживающий объектно-ориентированный, императивный и декларативный (например, функциональное программирование) стили.

В приведенном выше описании есть примечательный раздел: интерпретируемый или «точно вовремя скомпилированный язык программирования». От этого становится еще интереснее, потому что даже в одной из лучших документов четко не указано, интерпретируется ли JavaScript или компилируется, а скорее утверждается, что это может быть ОБА.

Прочитав последнее предложение, вы, наверное, скажете «это бред», но не спешите делать выводы.

JS-движки и как они работают

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

двигатель V8

V8 – механизм JavaScript с открытым исходным кодом и механизм WebAssembly, который перед выполнением компилирует код JavaScript в оптимизированный машинный код.

Чтобы добиться более высокой скорости выполнения JavaScript, V8 переводит код JS в более эффективный машинный код вместо использования интерпретатора. Большинство современных движков JavaScript, таких как SpiderMonkey или Rhino, используют тот же подход, но что выделяет V8, так это то, что он не создает никакого промежуточного кода. Из этого можно сказать, что V8 в некотором роде делает скомпилированную версию JavaScript.

Когда разработчик запускает JS-скрипт на V8, движок выполняет следующие шаги:

  1. Движок компилирует и выполняет код JS
  2. Движок обрабатывает стек вызовов
  3. Движок управляет кучей памяти
  4. Движок справляется со сборкой мусора
  5. Движок предоставляет все типы данных, объекты и функции
  6. Движок также предоставляет цикл обработки событий (иногда также реализуемый браузером).

Точная компиляция

Движок V8 получает свою скорость от JIT-компиляции JS-кода в собственный машинный код. Интерпретатор зажигания, ключевой компонент V8, компилирует код JS и генерирует неоптимизированный машинный код. Во время выполнения машинный код анализируется и перекомпилируется для достижения оптимальной производительности. Эта оптимизация осуществляется компонентами TurboFan и Crankshaft V8.

Двигатель носорога

public final void setOptimizationLevel(int optimizationLevel)

Установите текущий уровень оптимизации. Ожидается, что уровень оптимизации будет целым числом от -1 до 9. Любые отрицательные значения будут интерпретироваться как -1, а любые значения больше 9 будут интерпретироваться как 9. Уровень оптимизации -1 указывает, что режим интерпретации всегда будет использовал. Уровни от 0 до 9 указывают, что файлы классов могут быть созданы. Более высокие уровни оптимизации снижают производительность во время компиляции ради производительности во время выполнения. Уровень оптимизатора не может быть больше -1, если пакет оптимизатора не существует во время выполнения.

Rhino может преобразовывать сценарии JavaScript в классы. Rhino работает как в скомпилированном, так и в интерпретируемом режиме. Он предназначен для использования в настольных или серверных приложениях, поэтому в нем нет встроенной поддержки объектов веб-браузера, которые обычно ассоциируются с JavaScript.

ПаукОбезьяна Двигатель

Мне лично понравилась их документация и то, как они описали свой движок:

SpiderMonkey — это библиотека реализации JavaScript и WebAssembly для веб-браузера Mozilla Firefox. Поведение реализации определяется спецификациями ECMAScript и WebAssembly.

Чтобы оценить текст сценария, мы временно анализируем его с помощью Парсера в «Абстрактное синтаксическое дерево (AST), а затем запускаем BytecodeEmitter (BCE) для создания Байт-код и связанные метаданные. Мы называем этот результирующий формат Трафарет, и его полезная характеристика заключается в том, что он не использует сборщик мусора. Затем Stencil можно преобразовать в серию GC Cells, которые могут быть изменены и поняты механизмами выполнения, описанными ниже.

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

⚙️ Интерпретатор JavaScript

Байт-код, сгенерированный синтаксическим анализатором, может выполняться интерпретатором, написанным на C++, который манипулирует объектами в куче сборщика мусора и вызывает собственный код хоста (например, веб-браузера). См. [SMDOC] Определения байт-кода для описания каждого кода операции байт-кода и js/src/vm/Interpreter.cpp для их реализации.

⚡ JavaScript JIT

Чтобы ускорить выполнение байт-кода, мы используем серию компиляторов Just-In-Time (JIT) для создания специализированного машинного кода (например, x86, ARM и т. д.), адаптированного к JavaScript, который выполняется, и обрабатываемые данные.

Поскольку отдельный скрипт запускается больше раз (или имеет цикл, который выполняется много раз), мы описываем его как становящийся горячее, и при определенных пороговых значениях мы уровняем путем JIT-компиляции. . Каждый последующий уровень JIT тратит больше времени на компиляцию, но направлен на повышение производительности выполнения».

Подробнее о SpiderMonkey можно прочитать в их документации.

Заключение

Теперь, когда мы знаем, как работают JS-движки внутри. Первоначально, когда JavaScript только появился, у нас был движок JavaScript, такой как паук-обезьяна (ранние версии), созданный Бренденом Эйхом, который интерпретировал JavaScript в байт-код, и этот движок JavaScript мог работать внутри нашего браузера, чтобы сообщить нашим компьютерам, что делать. делать.

Но теперь все изменилось, у нас есть не только интерпретаторы, мы также используем компиляторы для оптимизации нашего кода. Итак, это распространенное заблуждение.

Когда кто-то говорит, что JavaScript — это интерпретируемый язык, да, в этом есть доля правды, даже когда кто-то говорит, что JavaScript — это компилируемый язык, в этом тоже есть доля правды, но в целом это зависит от реализации. Вы можете сделать реализацию JavaScript Engine, которая, возможно, только компилируется. Технически все зависит от реализации.

Честно говоря, «спорить о том, интерпретируется ли JavaScript или компилируется», на мой взгляд, пустая трата времени. У всех разные мнения и, так или иначе, они правы.