В этой статье будут рассмотрены переменные и области видимости в 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.