В этой статье будут рассмотрены переменные и области видимости в JavaScript и другие связанные вещи.

Глобальный охват.

Давайте объявим переменные var и let в нашем JS-файле. Если переменных нет в функции, как в примере ниже, они становятся частью Global Scope.

var a = 10;
let b = 10;

Это плохо по нескольким причинам.

Причина первая: время жизни переменных в Global Scope бесконечно, пока пользователь не закроет вкладку в браузере. Если вы храните много данных в Global Scope, это может повлиять на производительность и потребление памяти.

Причина вторая: объявление переменной в Global Scope без уважительной причины — эффективный способ написать плохой код, потому что код, написанный таким образом, сложнее поддерживать, тестировать и понимать.

Причина третья (только для var): если вы не используете «строгий режим» в своем JS-файле, var в Global Scope станет свойством объекта окна. При этом вы можете случайно перезаписать объектную переменную или функцию окна, и это приведет только к боли, страху и ошибкам.

Объем функции.

Переменная var ведет себя иначе, чем let и const при объявлении в функции.

function outer() {
  var x = 12;
  function inner() {
    console.log(x); // 12
  }
  if (2 + 2 == 4) {
    console.log(x); // 12
  }
  inner();
}
outer();

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

Область действия блока.

Противоположностью области действия является область действия блока. Когда вы объявляете переменную let, ее время жизни заканчивается закрывающей скобкой ее блока.

{
  let x = 10;
} // lifetime of x ends.

Кроме того, вы можете получить доступ к переменной x только внутри блока, где она была объявлена.

function func() {
  if (2 + 2 == 4) {
    let x = 10;
    console.log(x); // 10
  }
  console.log(x); // Uncaught ReferenceError: x is not defined
}
func();

константная переменная.

const совпадает с let, но вы не можете изменить значение переменной после инициализации.

const x = 10;
x = 5; // Uncaught TypeError: Assignment to constant variable.

:article-img{src="/blog/vars_scopes_js/meme_03.webp" alt="meme_03}

Временная мертвая зона (TDZ)

И тут начинается все самое интересное.

function simpleFunc() {
  console.log(x); // Reference error
  let x = 10;
}
simpleFunc();

А что произойдет, если мы изменим let на var?

function simpleFunc() {
  console.log(x); // undefined
  var x = 10;
}
simpleFunc();

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

Таким образом, JavaScript привязывает переменную или функцию к своей области видимости. Обратите внимание, что всплывает только объявление переменной, а не инициализация переменной! Так что же такое ТДЗ? TDZ — это термин, описывающий состояние, когда переменная/функция недоступна. Как и в приведенном выше примере, мы не можем получить доступ к переменной x до ее объявления, потому что console.log(x) находится во временной мертвой зоне. Кроме того, поведение var и let/const в TDZ отличается. В случае let/const вы получите ошибку ссылки при попытке доступа к переменной в TDZ. В случае с var получается undefined, что может привести к проблемам.

Аргументы в функциях и лексическом окружении.

Аргументы, которые вы передаете в функцию, присоединяются к области действия этой функции.

function func(x) {
  console.log(x); // undefined
}
var x = 'I'm outside the function!';
func();

Ух ты! Мы получаем undefined здесь! Причина в том, что мы не передаем значение в func() при вызове, но в объявлении функции func(x) имеет x в качестве аргумента. Поэтому, когда JavaScript вызывает console.log(x), он находит переменную x в области видимости функции. Но поскольку мы не передаем никакого значения в качестве аргумента, значение x равно undefined.

Немного изменим пример.

function func(a) {
  console.log(x); // I'm outside the function!
}
var x = 'I'm outside the function!';
func();

Итак, в этом примере JavaScript делает следующее: он ищет переменную x в области видимости функции и не находит ее. Затем JavaScript переходит во внешнюю область и ищет там переменную x, и теперь JavaScript находит ее и передает значение x в console.log();

Когда JavaScript ищет переменную, он начинает с локальной области видимости и перемещается во внешние области видимости, пока не достигнет глобальной области видимости. Если JavaScript не найдет переменную в Global Scope, вы получите ошибку ссылки.

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

Эта диаграмма условно показывает лексическое окружение, но может помочь вам понять это.

На этой диаграмме coffee и name являются переменными в лексическом окружении функций, а ref ведет к внешнему лексическому окружению. Ссылка Global Scope: null.

Закрытие.

Представьте, что мы хотим создать function, который подсчитывает, сколько мы выпили ледяного латте. Но есть одно условие, мы хотим сделать переменную счетчика доступной только внутри function.

function drinkLatte() {
  let counter = 0;
  return ++counter;
}
console.log(drinkLatte()); // 1
console.log(drinkLatte()); // 1
console.log(drinkLatte()); // 1

Вы заметили проблему? Доступна ли переменная счетчика только с помощью функции? Да. Этот код работает правильно? Нет. Каждый раз, когда мы вызываем drinkLatte(), он возвращает 1. Это работает, потому что время жизни переменной счетчика заканчивается закрывающей скобкой функции. Итак, в этом примере мы три раза создаем переменную-счетчик со значением 0, затем увеличиваем счетчик до значения 1 и возвращаемся. Нам нужен механизм, который может сохранять состояние переменных. Замыкания могут помочь нам в этом. Вот еще один пример использования замыканий.

function getLatte() {
  let counter = 0;
  return function drinkLatte() {
    return ++counter;
  };
}
const latte = getLatte();
console.log(latte()); // 1
console.log(latte()); // 2
console.log(latte()); // 3

Оно работает! Мы создали новую функцию с именем getLatte, которая обернула нашу предыдущую функцию. В функции getLatte объявлена ​​переменная-счетчик, и она возвращает функцию drinkLatte, которая увеличивает переменную-счетчик из внешней функции. Затем мы инициализируем переменную latte возвращаемой функцией, так что теперь мы можем вызывать функцию drinkLatte из этой переменной.

С помощью замыканий мы можем увеличить время жизни переменной и использовать ее во внутренних функциях.

Заключение.

Избегайте использования глобальных переменных без веской причины. Используйте let и const вместо var, так как это намного безопаснее. Помните о TDZ и помните о том, как работает лексическое окружение.

Вот и все! Спасибо за прочтение. Если вам понравилась статья, вы могли бы помочь Украине. Даже 1 доллар поможет.

Фонд Вернись живым: https://savelife.in.ua/ru/donate-ru/.

Первоначально опубликовано на https://xdevs.pro.