Подтвердить что ты не робот

Действительно ли браузер действительно читает JavaScript по очереди или делает несколько проходов?

Я понимаю, что JavaScript интерпретируется и не компилируется. Нет проблем. Тем не менее, я продолжаю читать здесь, что JavaScript выполняется "на лету" и эти строки читаются по одному. Эта идея меня немного запутывает, когда дело доходит до следующего примера:

writeToConsole();

function writeToConsole() {
    console.log("This line was reached.");
}

Для записи этот бит кода будет писать на консоль просто отлично. Тем не менее, как браузер знал бы о существовании exampleFunction(), если он еще не достиг функции?

Другими словами, когда именно эта функция сначала интерпретируется?

4b9b3361

Ответ 1

Во-первых, вы делаете неверное предположение: компилируется современный JavaScript. Двигатели типа V8, SpiderMonkey и Nitro компилируют JS-источник в native машинный код хост-платформы.

Даже в более старых версиях JavaScript не интерпретируется. Они преобразуют исходный код в bytecode, который выполняется с помощью виртуальной машины.

На самом деле, как работают на языках Java и .NET: когда вы "компилируете" ваше приложение, вы фактически преобразуете исходный код в байт-код платформы, Java bytecode и CIL соответственно. Затем во время выполнения компилятор JIT компилирует байт-код в машинный код.

Только очень старые и упрощенные JS-движки фактически interpret исходный код JavaScript, потому что интерпретация очень медленная.

Итак, как работает компиляция JS? На первом этапе исходный текст преобразуется в абстрактное синтаксическое дерево (AST), структуру данных, которая представляет ваш код в формате, который могут обрабатывать машины. Концептуально это очень похоже на то, как текст HTML преобразуется в представление DOM, с которым работает ваш код.

Чтобы генерировать AST, движок должен иметь дело с вводом необработанных байтов. Обычно это делается с помощью лексического анализатора. Лексер действительно не читает файл "по очереди"; скорее он читает байты за байтом, используя правила синтаксиса языка для преобразования исходного текста в токены. Затем лексер передает поток токенов в парсер , который фактически создает АСТ. Парсер проверяет, что токены образуют допустимую последовательность.

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

Как только двигатель имеет AST:

  • Интерпретатор может просто начать выполнять инструкции непосредственно из АСТ. Это очень медленно.
  • Реализация JS VM использует AST для генерации байт-кода, затем начинает выполнение байт-кода.
  • Компилятор использует AST для генерировать машинный код, который выполняет процессор.

Итак, теперь вы должны видеть, что, как минимум, выполнение JS происходит в два этапа.

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

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

Просто объявление функции - это когда вы объявляете новую функцию, которая будет вызываться в другом месте:

function foo() {

}

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

var foo = function() { };

$.get('/something', function() { /* callback */ });

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

Это может привести к очень любопытным поведением:

var foo = function() { console.log('bar'); };

function foo() { console.log('baz'); }

foo();

Что вы ожидаете от входа в консоль? Если вы просто читаете код линейно, вы можете подумать baz. Тем не менее, он будет фактически регистрировать bar, потому что объявление foo поднимается над выражением, которое присваивает foo.

Итак, заключаем:

  • Исходный код JS никогда не "читается" по очереди.
  • Исходный код JS фактически скомпилирован (в истинном смысле слова) в современных браузерах.
  • Двигатели компилируют код в несколько проходов.
  • Поведение вашего примера является побочным продуктом правил языка JavaScript, а не тем, как оно компилируется или интерпретируется.

Ответ 2

Все функции будут сначала проверены браузером перед выполнением любого кода.

Однако

var foo = function(){};

Это не будет проверено, и, таким образом, следующее будет бросать TypeError: undefined is not a function

foo();
var foo = function(){};

Ответ 3

Проходит 2 прохода. Первый проход анализирует дерево синтаксиса, часть которого выполняет подъем. Этот подъем делает то, что делает ваш опубликованный код работать. Подъем перемещает любые объявления var или именованные функции function fn(){} (но не выражения функций fn = function(){}) в начало функции, в которой они появляются.

Второй проход выполняет разбор, подъем, а в некоторых скомпилированных машинах дерево исходного кода.

Проверьте этот пример. Он показывает, как синтаксическая ошибка может предотвратить все выполнение вашего script, бросив ключ в первый проход, что предотвращает выполнение второго прохода (фактического выполнения кода).

var validCode = function() {
  alert('valid code ran!');
};
validCode();

// on purpose syntax error after valid code that could run
syntax(Error(

http://jsfiddle.net/Z86rj/

Нет alert() здесь. Первый синтаксический анализ проходит, и никакой код не выполняется.

Ответ 4

script сначала анализируется, затем интерпретируется, а затем выполняется. Когда выполняется первый оператор (writeToConsole();), объявление функции уже интерпретируется.

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

Ответ 5

JavaScript фактически интерпретируется по строкам. НО, прежде чем он будет выполнен, есть первый проход, сделанный компилятором, прочитав некоторые вещи (довольно уродливый, посмотрите на это: https://www.youtube.com/watch?v=UJPdhx5zTaw если вам действительно интересно).

Дело в том, что JavaScript будет сначала "прочитан" компилятором, который уже сохраняет функции, определенные как function foo(){...}. Вы можете вызвать их в любой момент времени в script, если вы вызываете их из той же или подчиненной области. То, что современные компиляторы также делают, - это предустановленные объекты, так что в качестве побочного эффекта имеет смысл сильно печатать ваши переменные в зависимости от производительности.

var foo = function(){...} не будет сохранен компилятором из-за того, что JavaScript слабо напечатан и тип переменной может измениться во время выполнения.