Модель выполнения JavaScript
JS часто классифицируется как язык с интерпретацией сценариев. Однако правда в том, что существует более одного способа интерпретации (каламбур предназначен и оставлен на ваше усмотрение).
Модели выполнения
Обычно при упоминании фразы «интерпретируемый язык» мы думаем о построчном выполнении исходного кода. В этой модели обработки каждая строка преобразуется в машинный код, преобразованная строка кода выполняется, и только после этого модель обработки переходит к следующей строке.
Существует еще одна модель обработки, называемая компиляцией, при которой весь исходный код сразу же преобразуется в машинные инструкции, и эти инструкции сохраняются в другом файле. После создания скомпилированного файла машинных инструкций при выполнении этого файла будет выполнен вывод исходного кода.
JS интерпретируется? Пролог к выполнению JS
По-прежнему остается вопрос, использует ли JS этот метод построчного преобразования кода с последующим выполнением, которое мы обычно называем «интерпретацией»? Что ж, ответ немного более тонкий, чем «да» или «нет». Механизмы JavaScript объединили обе вышеупомянутые модели обработки в способ выполнения JS. Несмотря на то, что эти движки не генерируют скомпилированный файл машинных инструкций, JS по-прежнему компилируется перед началом выполнения. Я знаю. Я знаю. Это было сложно в одном предложении, но просто дайте этой идее пять минут, и кусочки головоломки механизма выполнения JS внезапно начнут складываться. Помня о том, что JS сначала компилирует весь код, давайте продолжим.
Поведение, при котором JS сначала компилирует свой код, заметно через такие простые вещи, как «syntax errors» и «hoisting».
Создание синтаксической ошибки
console.log("Hello World"); // this won't be printed
var wrongJS => 'this will throw an error';
Если бы JS интерпретировался, преобразовывался и выполнялся построчно без перехода к следующей строке до завершения этого процесса, первая строка выводила бы на консоль «Hello World», потому что ошибка находится в строке 2. Но это не так. JS запускается построчно без предварительной компиляции и не выводится на консоль из-за синтаксической ошибки. Это один из таких примеров, показывающих, что здесь задействованы определенные элементы компиляции.
Подъем объявления функции
print_hello();
function print_hello(){
console.log("Hello");
}
Опять же, если бы JS интерпретировался построчно, он не мог бы смотреть вперед в строку 3, не выполнив строку 1. Это означало бы, что JS не знал бы, что print_hello()
находится в строке 1, и должен был по праву выдать ошибку ссылки. Но он не выдал ошибку, а вместо этого успешно выполнил функцию и напечатал на консоли.
Эти примеры явно пробивают некоторые дыры в теории о том, что JS - это строго интерпретируемый язык. Итак, означает ли это, что JS - это полностью компилируемый язык? Придержи лошадей. Как я уже сказал, JS-движки реализуют смесь обоих этих методов.
Заключение
Из приведенных выше свидетельств для особых случаев должно быть достаточно сказать, что движки JS имеют компилятор, который компилирует код в байтовый код, и этот байтовый код затем передается в интерпретатор, который генерирует машинный код для выполнения. Это высокоуровневое объяснение того, как запускается JS-код, не вдаваясь в детали базовых компиляторов, JIT-компиляторов, интерпретаторов и прочего.
Интересный факт: поскольку у JS-движков нет типичного этапа компиляции, заключающегося в предварительной компиляции, скомпилированный код не всегда оптимизирован, потому что у них не всегда так много времени на его оптимизацию. Следовательно, они используют оптимизирующие компиляторы для оптимизации повторяющихся фрагментов кода во время выполнения, отслеживая выполняемый код и данные, которые используются для выполнения.