И как вы тоже можете следить за своими переменными!

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

TL; DR: переменные, объявленные с помощью let и const, имеют блочную область видимости. Переменные, объявленные с помощью var, относятся к области действия функции. Никогда не объявляйте переменные неявно, так как они будут иметь глобальную область действия и перезапишут любые другие глобальные значения. И, наконец, имена переменных находятся путем поиска в самой внутренней области, и если соответствующие переменные не найдены, поиск во внешних областях будет осуществляться последовательно.

«Глобальная область действия JavaScript похожа на общественный туалет. Туда нельзя не зайти, но постарайтесь при этом ограничить контакт с поверхностями».
― Дмитрий Барановский

Теперь, когда вы успешно объявили и присвоили группу переменных, используя var, let и const, пришло время применить их. Давайте попробуем.

let x = 1; 
function addOne(num){
 console.log(num+1);
}
addOne(x); //-> "2"

Чудесный! Мы успешно получили доступ к переменной x и функции addOne , когда мы вызвали их обе внизу страницы. Но как мы определили, что наши вызовы addOne и x будут работать? Как оказалось, оба были в области действия при вызове.

Обобщенная область действия выглядит следующим образом: область действия — это доступность переменных в вашем коде.

Каждый раз, когда мы вызываем переменную, как мы это делали с addOne(x), движок JavaScript проверяет каталог переменных, к которым у нас есть доступ при выполнении вызова. Поскольку имена x и addOne доступны в области видимости при вызове функции, приведенный выше код работает без ошибок. Если бы одно из имен было вне области действия, мы бы получили сообщение об ошибке.

Давайте немного изменим приведенный выше код, чтобы увидеть это в действии.

{
   let b = 2;
   const c = 3;
   var d = 4;
   e = 5;
}
function addOne(x){
 console.log(x+1)
}
addOne(b) //-> Reference error, 'b' is not defined
addOne(c) //-> Reference error, 'c' is not defined
addOne(d) //-> "5"
addOne(e) //-> "6"

Вау! Здесь многое происходит. Я добавил блок с помощью фигурных скобок {}, который охватывает объявления наших переменных в верхней части кода. Когда мы пытаемся получить доступ к переменным во время вызовов нашей функции addOne в нижней части примера, вызовы переменных, объявленных с помощью let и const, терпят неудачу, в то время как другие выполняются успешно. Это связано с тем, что переменные, объявленные с помощью let и const, имеют блочную область видимости. Их значения доступны только внутри окружающего блока. Если объемлющего блока нет, то их значения доступны в глобальной области видимости. Если наш вызов addOne находится внутри фигурных скобок, код работает! Видеть:

{
   let b=2;
   const c=3;
   addOne(b) //-> 3
   addOne(c) //-> 4
}
function addOne(x){
 console.log(x+1)
}

При этом переменная, объявленная с помощью var, остается доступной вне блока. Это связано с тем, что переменные, объявленные с помощью var, относятся к области действия функции. Их имена доступны в любом месте внутри функции, которая их охватывает. Если они объявлены вне функции, то они имеют глобальную область видимости и доступны в любом месте вашего кода.

Взгляните на следующий код. Обратите внимание, что переменная, объявленная с помощью var, доступна в любом месте внутри функции myFunc, но не за пределами myFunc.

function myFunc(){
  {
    var a = 1; 
    let b = 2;
    const c = 3;
    e = 5;
  }
  console.log(a); // -> 1
  console.log(b); // -> Reference Error, b is not defined
  console.log(c); // -> Reference Error, c is not defined
  console.log(e); // -> 5
}
myFunc(); 
console.log(a); // -> Reference Error, a is not defined
console.log(e); // -> 5

В этом примере показана еще одна характеристика переменных JavaScript. При объявлении внутри функции переменные JavaScript являются локальными для этой функции. Это означает, что их имена доступны только после того, как была вызвана их родительская функция, и их имена привязаны к соответствующему блоку и функции.

Таким образом, переменные, объявленные с помощью let и const, доступны в любом месте в их охватывающем блоке, а переменные, объявленные с помощью var, доступны в любом месте внутри их охватывающей функции. Все три переменные имеют глобальную область видимости, если нет окружающих блоков или функций.

А как насчет необъявленной переменной e? К нему также можно получить доступ за пределами его охватывающего блока, и в приведенном выше примере к e можно получить доступ даже за пределами его охватывающей функции! Такие переменные, как e, обычно называют неявными глобальными переменными, и они особенно коварны для разработчиков.

Чтобы продемонстрировать, почему неявные глобальные переменные настолько вредны для нашего кода, давайте представим, что у нас есть функция с именем randomConsole, которая возвращает случайную игровую консоль. Наша функция создает переменную console и использует метод Math.random() для случайного выбора из списка игровых консолей. Переменная неявно создается и присваивается результату случайного выбора, а затем возвращается randomConsole.

function randomConsole(){
  let consoles = ['Xbox', 'PlayStation', 'Wii']; 
  console = consoles[Math.floor(Math.random()) * 2];
  return console;
}
console.log(randomConsole()); 
//-> Type Error: console.log is not a function

На первый взгляд можно было бы ожидать, что приведенный выше код работает полностью. Однако, что интересно, когда мы пытаемся зарегистрировать результат randomConsole, мы получаем сообщение об ошибке, в котором говорится, что «console.log не является функцией». В этом и заключается проблема с неявными глобальными переменными. Неявные переменные не являются локальными, в отличие от их явно объявленных аналогов. Они существуют в глобальной области и перезаписывают любые другие переменные, которые уже существуют в глобальной области — в данном случае объект консоли.

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

function randomConsole(){
  let consoles = ['Xbox', 'PlayStation', 'Wii']; 
  console = consoles[Math.floor(Math.random()) * 2];
  return console;
}
console.log('test') // -> 'test'
alert(randomConsole()); //-> 'PlayStation' is alerted!
alert(console); // -> 'PlayStation' is alerted, too!

Обратите внимание, как мы можем использовать метод console.log даже после объявления функции randomConsole? Это связано с тем, что переменные, определенные внутри функции, объявляются только после вызова функции. Когда мы пытаемся получить доступ к объекту console в последнем предупреждении в приведенном выше примере, мы возвращаем «PlayStation», потому что была вызвана функция randomConsole, и поскольку объект консоли был переназначен во время этого вызова. Вам не о чем беспокоиться, если вы явно объявляете все свои переменные.

Сила области видимости заключается в том, что мы можем контролировать доступность имен переменных в нашем коде. Благодаря переменным с локальной областью видимости нам не нужно беспокоиться о том, что переменные одной функции наступят на пятки переменных других функций. Это значительно снижает нагрузку на отслеживание имен переменных, объявленных в нашем коде. Мы можем с удовольствием повторно использовать имена переменных в наших функциях, не опасаясь, что наши ссылки на переменные будут неправильно поняты движком JavaScript.

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

let fruit = ['raspberry', 'tomato']; 
let vegetables = ['celery', 'spinach']; 
}
  let fruit = ['apple', 'blackberry', 'orange']; 
  console.log(fruit); // -> ['apple', 'blackberry', 'orange']
  console.log(vegetables); // -> ['celery', 'spinach'];
}

Глобальные переменные fruit и vegetable доступны внутри блока. Однако переменная фруктов, объявленная внутри блока, имеет приоритет над глобальной переменной фруктов, когда поиск переменной происходит во время console.log. Это происходит из-за протокола, согласно которому JavaScript принимает имена при поиске переменных. JavaScript всегда сначала ищет имена переменных в непосредственной области видимости. Если в текущей области не найдено совпадающих имен, JavaScript выполняет поиск на следующем уровне включающей области. В приведенном выше примере, когда просматривается переменная fruit, переменная fruit с областью действия блока находится в непосредственной области видимости, и процесс поиска завершается. Когда переменная vegetable просматривается в непосредственной области видимости, соответствующее имя переменной не найдено. Затем JavaScript выполняет поиск в следующей охватывающей области, где находит глобально доступную переменную vegetable. Это тонкая, но мощная функция области видимости JavaScript, которая позволяет нам управлять пространством имен наших переменных во встроенных функциях и блоках. Если вы когда-нибудь задавались вопросом, какое назначение переменных имеет приоритет, просто помните, что внутренние определения имеют приоритет.

Подводя итог: переменные, объявленные с помощью let и const, имеют блочную область видимости. Переменные, объявленные с помощью var, относятся к области действия функции. Никогда не объявляйте переменные неявно, так как они будут иметь глобальную область действия и перезапишут любые другие глобальные значения. И, наконец, имена переменных находятся путем поиска в самой внутренней области, и если соответствующие переменные не найдены, поиск во внешних областях будет осуществляться последовательно.

Спасибо за чтение, люди. Далее: закрытие