Все переменные и константы в JavaScript имеют определенную область видимости, в пределах которой они могут действовать.
Все переменные и константы, которые объявлены вне функций, являются глобальными:
<!DOCTYPE html> <html> <head> <meta charset="utf-8" /> <title>METANIT.COM</title> </head> <body> <script> var a = 5; let b = 8; const c = 9; function displaySum(){ var d = a + b + c; console.log(d); } displaySum(); // 22 </script> </body> </html>
Здесь переменные a и b и константа c являются глобальными. Они доступны из любого места программы.
А вот переменная d глобальной не является, так как она определена внутри функции и видна только в этой функции.
Для определения локальной области видимости в JavaScript используются фигурные скобки { }, которые создают блок кода. Этот блок кода может быть безымянным, может быть именнованным, например, функция, либо может представлять условную или циклическую конструкцию. Например, определение переменных в безымянном блоке кода:
{ var a = 5; let b = 8; const c = 9; }
Однако в этом случае поведение переменной зависит от способа ее определения (через var
или через let
) и от типа блока.
var определяет локальные переменные уровня функции, а let определяет локальные переменные
уровня блока кода (подобным образом const определяет константы уровня блока кода). Рассмотрим, в чем состоит отличие.
Переменные и константы, определенные внутри функции, видны (то есть могут использоваться) только внутри этой функции:
function print(){ var a = 5; let b = 8; const c = 9; console.log("Function print: a =", a); console.log("Function print: b =", b); console.log("Function print: c =", c); } print(); console.log("Global: a =", a); // Uncaught ReferenceError: a is not defined
Переменные a и b и константа c являются локальными, они существуют только в пределах функции. Вне функции их нельзя использовать, поэтому мы получим следующий консольный вывод:
Function print: a= 5
Function print: b= 8
Function print: c= 9
Uncaught ReferenceError: a is not defined
Здесь мы видим, что при попытке обратиться к переменной a
вне функции print()
, браузер выводит ошибку. При этом подобное поведение
не зависит от того, что это за переменная - var или let, либо это константа. Подобное поведение для всех переменных и констант одинаково.
С переменными, которые определяются в безымянных блоках кода, а также в циклах и условных конструкциях ситуация чуть сложнее.
Переменная, объявленная с помощью var, может использоваться вне блока:
// безымянный блок { var a = 5; } console.log("a =", a); // a = 5 // условная конструкция if(true){ var b = 6; } console.log("b =", b); // b = 6 // цикл for(let i = 0; i < 5; i++){ var c = 7; } console.log("c =", c); // c = 7
Единственное условие, что блок кода должен срабатывать, чтобы инициализировать переменную. Так, в примере выше условие в конструкции if и в цикле for установлено так, что блок этих конструкций будет выполняться. Однако, что если условие будет иным, и блок не будет выполняться?
if(false){ var b = 6; } console.log("b =", b); // b = undefined // цикл for(let i = 1; i < 0; i++){ var c = 7; } console.log("c =", c); // c = undefined
В таком случае мы опять же сможем обращаться к переменным, только они будут иметь значение undefined.
Теперь посмотрим, как будут вести себя в подобной ситуации переменные, определенные с помощью let:
{ let a = 5; } console.log("a =", a); // Uncaught ReferenceError: a is not defined
В данном случае мы получим ошибку. Мы можем использовать переменные let, определенные внутри блока кода, только внутри этого блока кода.
Тоже самое относится и к константам:
{ const b = 5; } console.log("b =", b); // Uncaught ReferenceError: b is not defined
Что если у нас есть две переменных - одна глобальная, а другая локальная, которые имеют одинаковое имя:
var z = 89; function print(){ var z = 10; console.log(z); // 10 } print(); // 10
В этом случае в функции будет использоваться та переменная z, которая определена непосредственно в функции. То есть локальная переменная скроет глобальную. Однако конкретное поведение при скрытии зависит от того, как определяется переменная.
Выше было указано, что var определяет переменную уровня функции. Поэтому с помощью оператора var мы НЕ можем определить одновременно две переменных с одним и тем же именем и в функции, и в блоке кода в этой функции. Если мы это сделаем, то при объявлении переменной на уровне блока мы меняем значение переменной уровня функции:
function displayZ(){ var z = 20; { var z = 30; // Не определяет новую переменную, а изменяет значение переменной z уровня функции console.log("Block:", z); } console.log("Function:", z); } displayZ();
Здесь определение переменной z внутри блока фактически будет равноценно изменению значения переменной уровня функции и фактически это будет одна и та же переменная. Консольный вывод:
Block: 30 Function: 30
Как писалось выше, оператор let определяет переменную уровня блока кода.
То есть каждый блок кода определяет новую область видимости, в которой существует переменная. Вне блока кода, где определена переменная, она не существует.
Соответственно мы можем одновременно определить переменную на уровне блока и на уровне функции (в отличие от var
):
let z = 10; function displayZ(){ let z = 20; { let z = 30; console.log("Block:", z); } console.log("Function:", z); } displayZ(); console.log("Global:", z);
Здесь внутри функции displayZ определен блок кода, в котором определена переменная z (вместо безымянного блока это мог быть и блок условной конструкции или цикла). Она скрывает глобальную переменную и переменную z, определенную на уровне функции.
И в данном случае мы получим следующий консольный вывод:
Block: 30 Function: 20 Global: 10
Все, что относится к оператору let, относится и к оператору const, который определяет константы уровня блока кода. Блоки кода задают область видимости констант, а константы, определенные на вложенных блоках кода, скрывают внешние константы с тем же именем:
const z = 10; function displayZ(){ const z = 20; { const z = 30; console.log("Block:", z); // 30 } console.log("Function:", z); // 20 } displayZ(); console.log("Global:", z); // 10
При выполнении кода, когда интерпретатор сталкивается с каким-то идентификатором (название переменной, константы, функции), то вначале он ищет опеределение этого идентификатора в текущей области видимости, благодаря чему собственно и работает скрытие переменных и констант. Например,
const z = 10; function displayZ(){ const z = 20; console.log(z); // 20 } displayZ(); // 20
Здесь интепретатор увидит, что в функции displayZ идет обращение к идентификатору z
, и будет искать определение этого идентификатора внутри функции displayZ
.
И поскольку в этой функции есть определение константы const z = 20
, то именно эта константа и будет использоваться.
Другой пример:
const z = 10; function displayZ(){ console.log(z); // 10 } displayZ(); // 10
Теперь внутри функции displayZ
нет определения идентификатора z
, поэтому для его поиска применяется scope chain
- интерпретатор обращается к окружающей
области видимости и выполняет поиск там. То есть смотри области видимости по цепочке от текущей - к внешним вплоть до глобальной области видимости.
При определении переменных в JavaScript мы можем не использовать ключевое слово let или var. Например:
<!DOCTYPE html> <html> <head> <meta charset="utf-8" /> <title>METANIT.COM</title> </head> <body> <script> { username = "Tom"; } console.log(username); // ошибки нет { console.log(username); // ошибки нет, доступна внутри других блоков кода } </script> </body> </html>
Если мы не используем это ключевое слово let/var при определении переменной в функции, то такая переменная будет глобальной. Например::
function setAge(){ userage = 39; } setAge(); console.log(userage); // 39
Несмотря на то, что вне функции setAge переменная userage нигде не определяется, тем не менее она доступна вне функции во внешнем контексте. Единственное условие - мы вызываем функцию, где определена такая переменная..
Однако если мы не вызовем функцию, переменная будет не определена:
function setAge(){ userage = 39; } // setAge(); Функция НЕ вызывается console.log(userage); // ошибка - Uncaught ReferenceError: userage is not defined
Ту же ошибку мы получили бы, если бы мы не только присваивали значение переменной, но и определяли бы ее как локальную по отношению к функции:
function setAge(){ var userage = 39; } setAge(); console.log(userage); // ошибка - Uncaught ReferenceError: userage is not defined
Определение глобальных переменных в функциях может вести к потенциальным ошибкам. Чтобы их избежать используется строгий режим или strict mode
.
Установить режим strict mode можно двумя способами:
добавить выражение "use strict" в начало кода JavaScript, тогда strict mode будет применяться для всего кода
добавить выражение "use strict" в начало тела функции, тогда strict mode будет применяться только для этой функции.
Глобальное применение strict mode
:
<!DOCTYPE html> <html> <head> <meta charset="utf-8" /> <title>METANIT.COM</title> </head> <body> <script> "use strict"; // используем строгий режим username = "Tom"; // Uncaught ReferenceError: username is not defined console.log(username); </script> </body> </html>
В этом случае мы получим ошибку SyntaxError: Unexpected identifier
, которая говорит о том, что переменная username не определена.
Аналогичную ошибку мы получим при определении глобальной переменной в функции:
<!DOCTYPE html> <html> <head> <meta charset="utf-8" /> <title>METANIT.COM</title> </head> <body> <script> "use strict"; // используем строгий режим function setAge(){ userage = 39; // Uncaught ReferenceError: userage is not defined } setAge(); console.log(userage); </script> </body> </html>
Пример использования строгого режима на уровне функции:
<!DOCTYPE html> <html> <head> <meta charset="utf-8" /> <title>METANIT.COM</title> </head> <body> <script> username = "Tom"; // норм console.log(username); // Tom function setAge(){ "use strict"; // используем строгий режим на уровне функции userage = 39; // Uncaught ReferenceError: userage is not defined } setAge(); console.log(userage); </script> </body> </html>