Давайте вспомним материал о переменных, пройденный несколько глав назад. Каждая объявленная нами переменная имеет конкретный уровень видимости, который определяет то, когда мы можем ее использовать. Выражаясь человеческим языком, это значит, что простое объявление переменной еще не дает нам возможности вызывать ее из любой части кода. Поэтому нужно понять несколько простых аспектов области видимости переменных.
В этом уроке я объясню области видимости переменных на примере распространенных случаев, большинство из которых мы уже встречали. Вообще, это достаточно обширная тема, но мы пробежимся по верхам. Время от времени эта тема будет всплывать в последующих уроках, так что мы сможем пополнить знания, полученные здесь.
Поехали!
Глобальная область видимости
Мы начнем с изучения самой обширной области, которая известна как глобальная область видимости. В реальной жизни, когда мы говорим, что что-то может быть услышано глобально, то имеем в виду, что услышим это «что-либо», находясь в любой точке мира.
В JavaScript все работает аналогично. Например, если мы говорим, что переменная доступна глобально, то имеем в виду, что у любого кода на нашей странице есть доступ для ее считывания или изменения. Если мы хотим что-либо определить глобально, то делаем это в коде, находящемся исключительно за пределами какой-либо функции.
Рассмотрим пример:
let counter = 0;
alert(counter);
Здесь мы просто объявляем переменную counter и инициализируем ее как 0. Поскольку эта переменная объявлена напрямую в теге script и не помещена в функцию, она считается глобальной. Это означает, что к этой переменной counter может обратиться любой код, находящийся в документе.
В следующем примере выделен этот аспект:
let counter = 0;
function returnCount() {
return counter;
}
alert(returnCount());
Мы видим, что переменная counter объявлена вне функции returnCount. Но, несмотря на это, у функции есть полный доступ к ней. При выполнении кода функция alert вызывает функцию returnCount, которая возвращает значение переменной counter.
Все это время мы использовали глобальные переменные, не придавая этому значения. В данной ситуации я всего лишь официально представил вам гостя, который тусуется на вашей вечеринке уже какое-то время.
Локальная область видимости
Становится еще интереснее, когда мы смотрим на элементы, не объявленные глобально. С этого момента понимание, что такое области видимости, начинает приносить свои плоды. Как мы видели ранее, к переменной, объявленной глобально, внутри функции также есть доступ:
let counter = 0;
function returnCount() {
return counter;
}
Но не наоборот. Переменная, объявленная внутри функции, не будет доступна извне:
function setState() {
let state = "on";
}
setState();
alert(state) // undefined
В этом примере переменная state объявлена внутри функции setState и обращение к ней извне не работает. Причина в том, что область видимости переменной state является локальной относительно самой функции setState. В более общей форме это можно описать, сказав, что переменная stateлокальна.
Использование переменных без объявления
Если мы инициализируем переменную state, не объявляя ее формально, область ее видимости будет определяться совсем иначе:
function setState() {
state = "on";
}
setState(); alert(state) // "on"
Хотя в этом случае переменная state и появляется внутри функции setState, сам факт того, что мы объявили ее без let, const (или var — устаревший способ объявления переменных), делает ее глобальной. По большому счету, вам не понадобится объявлять переменные подобным образом. Поэтому всегда используйте let или const.
Особенности областей видимости
Поскольку сейчас мы разбираем JavaScript, было бы слишком просто останавливаться на достигнутом понимании областей видимости. В последующих разделах я обозначу некоторые важные особенности.
Области блоков
Наш код состоит из блоков. Множества блоков! И вообще, что такое блок? Блок — это набор инструкций JavaScript, почти всегда заключенный в фигурные скобки. Например, посмотрим на следующий код:
let safeToProceed = false;
function isItSafe() {
if (safeToProceed) {
alert("You shall pass!");
} else {
alert("You shall not pass!");
}
}
isItSafe();
Если посчитать количество пар фигурных скобок, станет ясно, что здесь три блока. Один из них — это область, содержащаяся в самой функции isItSafe:
let safeToProceed = false;
function isItSafe()
if (safeToProceed) {
alert("You shall pass!");
} else {
alert("You shall not pass!");
}
}
isItSafe();
Второй блок — это область инструкции if:
let safeToProceed = false;
function isItSafe() {
if (safeToProceed) {
alert("You shall pass!");
} else {
alert("You shall not pass!");
}
}
Третий блок — это область, охваченная инструкцией else:
let safeToProceed = false;
function isItSafe() {
if (safeToProceed) {
alert("You shall pass!");
} else {
alert("You shall not pass!");
}
}
Любая переменная, объявленная внутри блока через let или const, является локальной для этого блока и всех содержащихся в нем дочерних блоков. Чтобы лучше это понять, посмотрите на следующий код — вариацию функции isItSafe:
function isThePriceRight(cost) {
let total = cost + 1;
if (total > 3) {
alert(total);
} else {
alert("Not enough!");
}
}
isThePriceRight(4);
Мы объявляем переменную total как часть блока функции, затем обращаемся к ней внутри блока if. Как вы думаете, к чему это приведет? Переменная total полностью (ха-ха!) доступна отсюда, так как блок if является дочерним блоком блока функции. Выражаясь корректно, скажем, что переменная total находится в области функции alert.
А что насчет следующей ситуации?
function isThePriceRight(cost) {
let total = cost + 1;
if (total > 3) {
let warning = true;
alert(total);
} else {
alert("Not enough!");
}
alert(warning);
}
isThePriceRight(4);
У нас есть переменная warning, объявленная внутри блока if, а еще — функция alert, которая пытается вывести значение warning. В этом случае, так как мы пытаемся обратиться к переменной warning в блоке, находящемся вне блока, в котором она была объявлена, функция alert не будет отображать значение true. Учитывая то, где находится функция alert, переменная warning находится вне области видимости.
объявление переменных с ключевым словом var
Несколько абзацев выше я упомянул вскользь, что переменные когда-то объявлялись с помощью ключевого слова var. Ключевые слова let (и const) были введены позднее для облегчения объявления переменных. Если вы до сих пор используете var, пора переходить на let. Мы еще не обсуждали, почему let — более предпочтительный вариант, и решили, что обсудим это позже, когда начнем рассматривать области переменных подробнее. Что ж, настал этот момент!
Переменные, объявленные с var, распространяются на область функции. Они не распространяются на блоки, подобные инструкциям if и else. Если мы изменим последний пример, объявив в нем переменную warning с var вместо let, то код будет выглядеть так:
function isThePriceRight(cost) {
let total = cost + 1;
if (total > 3) {
var warning = true;
alert(total);
} else {
alert("Not enough!");
}
alert(warning);
}
isThePriceRight(4);
В изначальном варианте этого кода функция alert в отношении переменной warning ничего бы не отобразила, так как эта переменная, будучи объявленной с let, оказывалась вне области видимости. При использовании же var ситуация меняется и вы увидите отображение true. В этом и есть отличие между var и let. Область видимости переменных, объявленных с var, ограничивается уровнем функций, поэтому если переменная объявляется в любом месте внутри функции, то считается, что она находится в области видимости. Область же видимости переменных, объявленных с let, как мы увидели ранее, определяется уровнем блока.
Степень свободы, с которой переменная var определяет область видимости, слишком большая, и поэтому легко допустить ошибку, связанную с переменными. По этой причине я ратую за использование let, когда дело доходит до объявления переменных.
Как JavaScript обрабатывает переменные
Если вам показалось, что логика областей блока из предыдущего раздела выглядит странно, значит, вы еще не видели следующий пример. Взгляните на этот код:
let foo = "Hello!";
alert(foo);
Глядя на него, мы можем обоснованно утверждать, что отобразится значение Hello! и в целом будем правы. А что, если переместить объявление и инициализацию переменной в конец?
alert(foo);
let foo = "Hello!";
В этом случае код выдаст ошибку. Доступ к переменной foo осуществляется без обращения к ней. А теперь давайте заменим let на var, получив следующее:
alert(foo);
var foo = "Hello!";
При выполнении этого варианта кода его поведение будет отличаться от предыдущего и вы увидите отображение undefined. Что конкретно здесь происходит?
Когда JavaScript при выполнении достигает определенной области (глобальной, функции и т. д.), то первое, что он делает, — полностью сканирует тело кода в поиске объявленных переменных. Если он встречает переменные, объявленные с var, то по умолчанию инициализирует их как undefined. Если же они объявлены с let или const, он оставляет их полностьюнеинициализированными. При завершении он перемещает все встреченные переменные в верхнюю часть соответствующей им области, которой в случае с let и const является ближайший блок, а в случае с var — ближайшая функция.
Давайте подробнее рассмотрим, что это значит. Изначально наш код выглядит так:
alert(foo);
let foo = "Hello!";
Когда JavaScript приступает к его обработке, код принимает следующий вид:
let foo;
alert(foo);
foo = "Hello!";
Несмотря на то что переменная foo была объявлена в нижней части кода, она смещается вверх. Формально это называется поднятием переменной. Особенность let и const в том, что при поднятии переменные остаются неинициализированными. Если вы попробуете обратиться к неинициализированной переменной, то код выдаст ошибку и прекратит выполнение. Если мы изменим предыдущий пример, используя var, то в итоге для JavaScript код будет выглядеть так:
var foo = undefined;
alert(foo);
foo = "Hello!";
Переменная по-прежнему поднимается, но при этом инициализируется как undefined, благодаря чему код продолжает выполнение.
Главная мысль из всего этого: пожалуйста, объявляйте и инициализируйте переменные прежде, чем их использовать. Несмотря на то что JavaScript способна в некоторой степени справиться с работой в тех случаях, когда мы пренебрегаем данными действиями, это лишь добавит еще больше путаницы.
Замыкания
Ни одно обсуждение области переменных не будет полным, если не рассмотреть замыкания. Сейчас я не стану объяснять эту тему, прибережем ее для главы 9.
Прежде чем вы продолжите читать дальше, убедитесь, что весь пройденный материал вам понятен. Если у вас есть какие-то вопросы, обращайтесь на форум https://forum.kirupa.com, где я и другие разработчики с радостью помогут вам.
КОРОТКО О ГЛАВНОМ
Место размещения переменных в коде имеет огромное влияние на то, где они могут использоваться. Переменные, объявленные глобально, доступны по всему приложению. Переменные, объявленные локально, — только внутри области, в которой расположены. Внутри диапазона глобальных и локальных переменных у JavaScript есть огромное количество действий в запасе.
Эта глава представила обзор аспектов влияния области переменных на ваш код. В ближайшем будущем вам предстоит встретиться с некоторыми наиболее актуальными из этих аспектов.