ES6: Блочные области видимости
До введения стандарта ES6 основой всех областей видимости являлись функции. У любой функции существует своя область видимости. Проще всего это можно рассмотреть на примере:
var num = 10;
var func = function() {
var num = 20;
console.log(num); // 20
};
console.log(num); // 10
Объявленные с помощью ключевого слова var
переменные внутри функции не влияют на переменные из других областей видимости, в том числе и глобальной. Именно на этом свойстве основана хорошая практика обворачивания всего кода в самовызывающуюся анонимную функцию (self-executing anonymous function):
(function() {
'use strict';
// Переменные a и b находятся в области видимости
// самовызывающейся анонимной функции и не доступны
// на более высоких уровнях
var a = 10;
var b = 20;
// Для вывода переменной в глобальную область видимости
// используется подобная конструкция
window.b = b;
})();
console.log(a); // undefined
console.log(b); // 20
Подобное решение позволяет полностью контролировать, какие переменные будут переданы в глобальное окружение. Тем не менее, данные правила работали исключительно с функциями, а на другие блочные конструкции не действовали:
for (var i = 0; i < 5; i++) {
console.log('Что-то было сделано ' + i + ' раз'); // 0 1 2 3 4
}
console.log('Переменная i до сих пор доступна и равна ' + i); // 5
Пример с циклом for
относительно безвреден и после последней итерации оставляет переменную i
. Подобное использование циклов широко распространено и, скорее всего, не станет причиной ошибки. Неочевидные вещи начинают появляться при использовании других блочных конструкций:
if (true) {
var a = 10;
}
console.log(a); // 10
Очевидно, что после запуска данного кода будет создана переменная a
, содержащая в себе число 10
. Все становится не так очевидно, когда условие переданное в конструкцию if
не является правдивым:
if (false) {
var a = 10;
}
console.log(a); // ???
Какой результат можно ожидать? Код внутри конструкции if
не запускался, а значит и перменная a
не была инициализирована. Логично предположить, что единственным возможным результатом является ошибка (попытка обратиться к несуществующей переменной обычно выдает ReferenceError
). Тем не менее, подобной ошибки не возникает, и в консоль выводится undefined
. Такое поведение объясняется поднятием переменных (hoisting). Конструкция, указанная выше, интерпретируется следующим образом:
var a;
if (false) {
a = 10;
}
console.log(a); // undefined
Оператор let
С релизом стандарта ES6 появилась возможность создавать переменные, приуроченные к отдельным блокам. Это означает, что для создания лексического окружения (scope) достаточно просто обвернуть код в фигурные скобки: { ... }
:
var a = 10;
{
let a = 20;
console.log(a); // 20
}
console.log(a); // 10
Скорее всего, вы никогда не будете использовать конструкцию, показанную выше, но, тем не менее, она является абсолютно валидной и позволяет наглядно продемонстрировать создание нового лексического окружения.
Таким образом, при выполнении следующего кода переменная i
не будет доступна вне цикла:
for (let i = 0; i < 5; i++) {
console.log(i); // 0 1 2 3 4
}
console.log(i); // ReferenceError: i is not defined
Подобное поведение будет наблюдаться и в других блочных конструкциях:
if (true) {
let num = 10;
console.log(num); // 10
}
console.log(num); // ReferenceError: num is not defined
Hoisting
При использовании ключевого слова let
происходит поднятие переменных (hoisting). Но сам процесс поднятия реализуется совершенно другим образом:
if (true) {
console.log(b);
let b = 10; // ReferenceError: b is not defined
}
Запустив подобный пример вы скорее всего можете прийти к выводу, что поднятия не происходит. Это не так. Для того, чтобы убедиться в наличии поднятия достаточно объявить еще одну переменную вне блока:
let b = 20;
if (true) {
console.log(b);
let b = 10; // ReferenceError: b is not defined
}
Несмотря на то, что переменная b
была объявлена вне блока и, таким образом, должна быть доступна, результатом выполнения кода все равно является ReferenceError
. Подобное поведение называется “временной мёртвой зоной”.
При запуске кода из блока происходит резервирование имён всех переменных объявленных в любом месте блока. Таким образом, несмотря на наличие внешней переменной, результатом выполнения кода будет ReferenceError
. Чтобы избежать подобных ошибок, всегда объявляйте все используемые переменные в самом начале блока:
if (true) { let num = 20;
console.log(num); // 20
}
Временная мёртвая зона не распространяется на функции до первого их вызова:
var f = function() {
return num;
};
let num = 10;
console.log(f()); // 10
Тем не менее, если вы попытаетесь вызвать функцию f
до того, как будет объявлена переменная num
, то всё равно получите ошибку:
var f = function() {
return num;
};
console.log(f()); // ReferenceError: num is not defined
let num = 10;
Оператор const
Оператор const
, как и let
, работает с блочными областями видимости (также подвергается правилам временной мёртвой зоны) и предназначен для создания констант - переменных, для которых доступно только чтение после их инициализации:
{
const num = 10;
console.log(num); // 10
num = 20; // TypeError: 'num' is read-only
}
Новое присваивание значения переменной num
выведет ошибку. Таким образом, значение, записанное в переменную при её инициализации, невозможно изменить с помощью присваивания. Создание новых переменных с таким же именем также выведет ошибку:
const a = 20;
let a = 10; // TypeError: 'a' is read-only
Заново инициализировать переменную с помощью оператора const
тоже не получится:
let a = 10;
const a = 10; // TypeError: Identifier 'a' has already been declared
Константы непостоянны
На первый взгяд, может показаться, что всё, что было записано в константу невозможно изменить. Но, на самом деле, нельзя менять только литерал или ссылку:
const lit = 4;
lit = 5; // TypeError: Литерал изменить нельзя
const obj = { a: 1 };
obj.a = 2; // Значения внутри объекта изменить можно
console.log(obj); // { a: 2 }
obj = { a: 3 }; // TypeError: Ссылку менять нельзя
const arr = [1, 2, 3];
arr.push(4); // Значения внутри массива изменить можно
console.log(arr); // [1, 2, 3, 4]
obj = [4, 3, 2, 1]; // TypeError: Ссылку менять нельзя
Чтобы сделать константу, содержащую объект, настоящей константой слудует использовать Object.freeze()
.
Ссылки по теме
- Перевод статьи ES6 Let, Const and the “Temporal Dead Zone” (TDZ) in Depth (ES6: Let, Const и «Временная мёртвая зона» (ВМЗ) изнутри) от css-live.ru
- Временная мёртвая зона
- Object.freeze()
- Константы в JavaScript − когда они нужны и где их использовать
Комментарии