Начнем с простого вопроса:
<script type="text/javascript">
предупреждение(я); // ?
вар я = 1;
</скрипт>
Выходной результат не определен. Это явление называется «предварительным анализом»: механизм JavaScript сначала анализирует переменные var и определения функций. Код не выполняется до завершения предварительного анализа. Если поток документов содержит несколько сегментов кода сценария (код js, разделенный тегами сценария или импортированные файлы js), порядок выполнения следующий:
шаг 1. Считайте первый сегмент кода.
Шаг 2. Выполните синтаксический анализ. В случае возникновения ошибки будет сообщено о синтаксической ошибке (например, несовпадающие скобки и т. д.) и будет выполнен переход к шагу 5.
шаг 3. Выполните «предварительный анализ» определений переменных и функций var (об ошибках не будет сообщено, поскольку анализируются только правильные объявления).
шаг 4. Выполните сегмент кода и сообщите об ошибке, если она есть (например, переменная не определена).
шаг 5. Если есть еще один сегмент кода, прочитайте следующий сегмент кода и повторите шаг 2.
Шаг 6. В конце приведенного выше анализа удалось объяснить многие проблемы, но я всегда чувствую, что чего-то не хватает. Например, что такое «предварительный анализ» на шаге 3? А на шаге 4 посмотрите на следующий пример:
<script type="text/javascript">
alert(i); // ошибка: я не определен.
я = 1;
</скрипт>
Почему в первом предложении возникает ошибка? Разве в JavaScript переменные не должны быть неопределенными?
Время процесса компиляции пролетело как белая лошадь, и я открыл «Принципы компиляции» рядом с книжным шкафом, как будто это был другой мир. На знакомом, но незнакомом пустом месте была такая заметка:
Для традиционных компилируемых языков. этапы компиляции делятся на: лексический анализ и синтаксический анализ, семантическую проверку, оптимизацию кода и генерацию байтов.
Но для интерпретируемых языков после того, как синтаксическое дерево получено посредством лексического и синтаксического анализа, можно начинать интерпретацию и выполнение.
Проще говоря, лексический анализ заключается в преобразовании потока символов (потока символов) в поток токенов (поток токенов), например преобразование c = a - b в:
NAME "c";
РАВНО
ИМЯ "а"
МИНУС
ИМЯ "б"
Точка с запятой
Вышеупомянутое — это всего лишь примеры. Для получения дополнительной информации см. Лексический анализ.
Глава 2 «Полного руководства по JavaScript» рассказывает о лексической структуре, которая также описана в ECMA-262. Лексическая структура является основой языка и ее легко освоить. Что касается реализации лексического анализа, это другая область исследований, которая здесь не будет рассматриваться.
Мы можем использовать аналогию с естественным языком. Лексический анализ — это жесткий перевод «один к одному». Например, если абзац с английского языка переводится на китайский слово за словом, мы получаем кучу потоков токенов, что сложно. понять. Дальнейший перевод требует грамматического анализа. Следующий рисунок представляет собой синтаксическое дерево условного оператора:
Если при построении синтаксического дерева обнаруживается, что его невозможно построить, например if(a { i = 2; }), будет сообщено о синтаксической ошибке и анализ всего блока кода завершится. Это шаг 2 в в начале этой статьи.
С помощью синтаксического анализа постройте После синтаксического дерева переведенное предложение все еще может быть неоднозначным, и потребуется дальнейшая семантическая проверка. Для традиционных строго типизированных языков основной частью семантической проверки является проверка типа. фактические параметры функций и соответствие формальных типов параметров. Для слабо типизированных языков этот шаг может быть недоступен (у меня ограничены силы и нет времени смотреть реализацию движка JS, поэтому я не уверен, существует ли такой шаг). этап проверки семантики в движке JS)
. Получается, что для движков JavaScript должен быть лексический анализ и анализ синтаксиса, а затем могут быть такие этапы, как семантическая проверка и оптимизация кода. После завершения этих этапов компиляции (в любом языке есть. процесс компиляции, но интерпретируемые языки не компилируются в двоичный код), код начнет выполняться.
Вышеупомянутый процесс компиляции все еще не может объяснить «предварительный анализ» в начале статьи. Нам придется внимательно изучить выполнение. Процесс кода JavaScript,
сказал Чжоу Айминь в «Сущности языка JavaScript». Во второй части «Практики программирования» есть очень тщательный анализ этого. Вот некоторые из моих выводов:
посредством компиляции код JavaScript был. преобразуется в синтаксическое дерево, а затем немедленно выполняется в соответствии с синтаксическим деревом,
что требует дальнейшего выполнения. Понимание механизма области действия JavaScript. С точки зрения непрофессионала, область действия переменных JavaScript определяется при их определении. То есть лексическая область видимости зависит от исходного кода. Компилятор может определить ее посредством статического анализа, поэтому лексическую область видимости также называют статической областью действия. и eval не может быть реализовано только с помощью статической технологии. Фактически, мы можем говорить только о механизме области видимости JS.
Когда движок JS выполняет каждый экземпляр функции, он создает контекст выполнения. Объект вызова — это структура scriptObject, которая используется для сохранения структур внутреннего анализа синтаксиса, таких как varDecls, таблица встроенных функций funDecls и upvalue родительского списка ссылок (примечание: такая информация, как varDecls и funDecls, получается во время объекта вызова. на этапе синтаксического анализа и сохраняются в синтаксическом дереве. При выполнении экземпляра функции эта информация будет скопирована из синтаксического дерева в объект сценария). Объект сценария — это статическая система, связанная с функцией, соответствующая жизненному циклу экземпляра функции. .
Лексическая область видимости — это механизм области видимости JS, и вам также необходимо понимать метод его реализации. Это цепочка областей видимости. Цепочка областей — это механизм поиска имени. Сначала он ищет объект сценария в текущей среде выполнения. Если он не найден, он следует по значению up до родительского объекта сценария и ищет глобальный объект.
Когда экземпляр функции выполняется, замыкание создается или связывается с ним. scriptObject используется для статического сохранения таблиц переменных, связанных с функциями, а закрытие динамически сохраняет эти таблицы переменных и их текущие значения во время выполнения. Жизненный цикл замыкания может быть дольше, чем у экземпляра функции. Экземпляр функции будет автоматически уничтожен после того, как активная ссылка станет пустой, а закрытие будет переработано механизмом JS после того, как ссылка на данные станет пустой (в некоторых случаях оно не будет автоматически перезапущено, что приведет к утечке памяти).
Не пугайтесь множества существительных, приведенных выше. Как только вы поймете концепции среды выполнения, объекта вызова, замыкания, лексической области видимости и цепочки областей действия, многие явления в языке JS можно будет легко решить.
Резюме На этом этапе вопросы, поставленные в начале статьи, могут быть объяснены очень ясно:
так называемый «предварительный анализ» на этапе 3 фактически завершается на этапе синтаксического анализа на этапе 2 и сохраняется в синтаксическом дереве. При выполнении экземпляра функции varDelcs и funcDecls будут скопированы из синтаксического дерева в scriptObject среды выполнения.
На шаге 4 неопределенные переменные означают, что их невозможно найти в таблице переменных scriptObject. Механизм JS будет искать вверх по значению scriptObject. Если ни одно из них не найдено, операция записи i = 1 будет эквивалентна окну. i = 1; добавляет новый атрибут к объекту окна. Для операций чтения, если объект сценария, который отслеживается обратно в глобальную среду выполнения, не может быть найден, произойдет ошибка времени выполнения.
После понимания туман рассеялся и расцвели цветы, а небо прояснилось.
Наконец, я оставляю вам вопрос:
<script type="text/javascript">
вар аргумент = 1;
функция foo(arg) {
предупреждение (аргумент);
вар аргумент = 2;
}
Фу(3);
</скрипт>
Каков вывод оповещения?