Немного о теории компиляторов
Код, перед выполнением проходит 3 стадии обработки, которые приближенно обьединяются термином компиляция:
- Лексический анализ / разбиение на токены ( token ) разбиение последовательности символов на осмысленные (с точки зрения языка) фрагменты, называемые токенами. Для примера возьмем программу var a = 2;. Скорее всего, эта программа будет разбита на следующие токены: var, a, =, 2 и ;. Пропуски могут сохраняться в виде токенов, а могут и не со- храняться в зависимости от того, имеет это смысл или нет.
- Разбор ( parsing) преобразование потока (массива) токенов в дерево вложенных элементов, которые в совокупности представляют грамматическую структуру программы. Это дерево называется «абстрактным деревом синтаксиса», или AST(Abstract Syntax Tree). Скажем, дерево для var a = 2; может начинаться с узла верхнего уровня VariableDeclaration, который содержит дочерний узел Identifier (со значением a) и другой дочерний узел AssignmentExpression, у которого есть свой дочерний узел с именем NumericLiteral (его значение равно 2).
- Генерирование кода — процесс преобразования AST в исполняемый код. Эта часть сильно зависит от языка, целевой платформы и т. д.
Движок JavaScript не ограничивается только этими 3 этапами . Это общая картина.
Область видимости
Участники процесса: 1. движокJS - отвечает за всю компиляцию от начала до конца и выполнения программы JS. 2. компилятор - разбирает и генерирует код. 3. областьВидимости - собирает и ведет список всех обьявленных идентификаторов(переменных) и устанавливает строгий набор правил их доступности для кода, выполняемого в данный момент.
Например,
let a = 2
- Когда компилятор обнаружил let a , он обращается к области видимости , чтобы узнать, существует ли переменная A в наборе этой конкретной области видимости .
- Если переменная существует: компилятор игнорирует объявление и двигается дальше.
- Если переменная не существует: компилятор обращается к области видимости для объявления новой переменной с именем a , в наборе этой области видимости.
- компилятор генерирует код для последующего выполнения . Движок обрабатывает присваивание a=2. Код спрашивает у области видимости доступна ли переменная с именем A в наборе текущей области видимости,
- Если переменная доступна, то движокJS использует эту переменную.
- Если нет, то движок ищет переменную в другом месте.
- Если движокJS находит переменную , он присваивает ей значение 2. А если нет, то сообщает об ошибке.
Когда Движок выполняет код, сгенерированный Компилятором на шаге 2, он должен провести поиск переменной a и определить, была ли она объявлена; этот поиск называется проверкой Области видимости. Однако тип проверки, выполняемой Движком, влияет на результат поиска.
В нашем примере Движок будет выполнять LHS-поиск переменной a. Другая разновидность поиска называется RHS-поиск. Сокращения означают «LeftHand Side» (левосторонний) и «RightHand Side» (правосторонний).
«Левая/правая сторона присваивания» в обозначениях LHS и RHS не обязательно буквально означает «левая/правая сторона оператора присваивания =». Присваивание также может выполняться другими способами, поэтому лучше концептуально рассматривать их как «приемник присваивания» (LHS) и «источник присваивания» (RHS).
Возьмем следующую программу, в которой задействованы как LHS-, так и RHS-ссылки:
function foo(a) {
console.log( a ); // 2
}
foo( 2 );
Последняя строка с вызовом функции foo(..) также требует RHS-ссылки на foo, которая означает «Найти значение foo и предоставить его мне». Более того, (..) означает, что значение foo должно быть выполнено, а значит, это должна быть функция!
Упражнение
- Найдите все LHS-поиски (всего 3).
- Найдите все RHS-поиски (всего 4).
function foo(a) {
var b = a;
return a + b;
}
var c = foo( 2 );
Вложенная область видимости
Чтобы наглядно представить процесс разрешения вложенных областей видимости, вообразите высокое здание.
Это здание изображает набор правил вложенных областей видимости. Первый этаж — текущая область видимости, какой бы она ни была, а верхний этаж — глобальная область видимости.
Чтобы разрешить LHS- или RHS-ссылку, система начинает поиск с текущего этажа. Если переменная не будет найдена, поиск поднимается на следующий этаж, ищет там, потом на следующем и т. д. Добравшись до верхнего этажа (глобальной области видимости), вы либо находите искомое, либо не находите. В любом случае здесь придется остановиться.
Почему важно отличать LHS от RHS?
Потому что эти два типа поиска по-разному ведут себя в ситуации, когда переменная еще не была объявлена (не найдена ни в одной из просмотренных областей видимости).
Пример:
function foo(a) {
console.log( a + b );
b = a;
}
foo( 2 );
Когда RHS-поиск для b происходит впервые, он завершается неудачей. Переменная, не найденная в области видимости, считается «необъявленной». Если RHS-поиск не находит переменную ни в одной из вложенных областей видимости, движок выдает ошибку ReferenceError. Важно заметить, что ошибка относится именно к типу ReferenceError.
С другой стороны, если движок выполняет LHS-поиск и прибывает на верхний этаж (глобальная область видимости), так и не обнаружив искомое, если программа не выполняется в строгом режиме strict, в глобальной области видимости создается новая переменная с указанным именем, которая передается движку.
«Нет, такой переменной не было, но я хочу помочь и создать ее для тебя».
Режим strict, добавленный в ES5, во многих отношениях отличается от обычного/нестрогого режима.
Одно из отличий заключается в том, что он запрещает автоматическое/неявное создание глобальных переменных. В этом случае LHS-поиск не вернет переменную с глобальной областью видимости, и движок выдаст ошибку ReferenceError по аналогии со случаем RHS.
Если переменная будет найдена для RHS-поиска, но вы пытаетесь сделать с ее значением нечто невозможное (например, попытка выполнить как функцию значение, которое функцией не является, или обращение к свойству для значения null или undefined), движок выдаст другую разновидность ошибки — TypeError.
Ошибка ReferenceError относится к проблемам при разрешении области видимости, а ошибка TypeError подразумевает, что разрешение области видимости прошло успешно, но была сделана попытка выполнить с результатом недопустимую/невозможную операцию.