let
и const
- два новых способа объявления переменных в JavaScript. let
похож на var
, но позволяет избежать некоторых распространенных ошибок. const
же является чуть расширенной версией let
в части запрета повторного присвоения значения переменной.
Оператор var
Традиционно объявление переменных в JavaScript производится с помощью ключевого слова var
:
var a = 10;
Область видимости
var
объявление имеет немного странные правила видимости переменных.
function f(shouldInitialize: boolean) {
if (shouldInitialize) {
var x = 10;
} else {
var x = 0;
}
return x;
}
console.log(f(true)); // 10
console.log(f(false)); // 0
Здесь переменная x
объявлена в if-else
блоке, но доступ к ней мы получаем извне этого блока. Это происходит потому, что объявленная как var
переменная доступна везде в пределах функции, модуля, пространства имен или глобальной области, где она объявлена, независимо от блока, в котором она расположена. И вот эта штука может служить причиной некоторых косяков.
Причуды замыкания переменных
Гляньте на этот код:
for (var i = 0; i < 10; i++) {
setTimeout(function() {console.log(i); }, 100 * i);
}
Что будет выведено в консоли?
10
10
10
10
10
10
10
10
10
10
Вот что!
setTimeout
вызывает анонимную функцию по прошествии некоторого времени, когда цикл for
уже выполнен. К этому времени переменная i
уже равняется 10 и функция использует это значение. Все 10 раз.
Для решения этой проблемы обычно применяется так называемая немедленно вызываемая функция, которая захватывает значение переменной i
на каждой итерации в свою собственную область видимости:
for (var i = 0; i < 10; i++) {
(function(i) {
setTimeout(function() { console.log(i); }, 100 * i);
})(i);
}
Такие дела.
Оператор let
Мы убедились, что var
имеет некоторые проблемы. Вот поэтому и появился новый способ объявления переменных - оператор let
.
let hello = 'Здравствуйте!';
Область видимости
Если переменная объявлена с использованием оператора let
, то ее область видимости ограничена блоком, в котором она объявлена.
function f(input: boolean) {
let a = 100;
if (input) {
// C переменной 'a' все хорошо
let b = a + 1;
return b;
}
// А тут ошибка: переменной 'b' отсюда не видно
return b;
}
Еще одно свойство таких переменных состоит в том, что они не могут быть записаны или прочитаны до объявления:
a++; // Ошибка: нельзя использовать переменную 'a' до ее объявления;
let a;
Повторное объявление
C использованием var
переменную можно объявить несколько раз. Такая возможность может привести к появлению нежелательных последствий.
let
не допускает подобных вольностей:
let x = 10;
let x = 20; // Ошибка: нельзя переобъявить переменную 'x'
И не обязательно, что бы обе переменные были объявлены как let
:
function f(x) {
let x = 100; // Ошибка: двойное объявление 'x'
}
function g() {
let x = 100;
var x = 100; // Ошибка: нельзя переобъявить переменную 'x'
}
Перекрытие или затенение переменных
function sumMatrix(matrix: number[][]) {
let sum = 0;
for (let i = 0; i < matrix.length; i++) {
var currentRow = matrix[i];
for (let i = 0; i < currentRow.length; i++) {
sum += currentRow[i];
}
}
return sum;
}
Данный код будет работать корректно, так как переменная i
внутреннего цикла затеняет переменную i
внешнего цикла.
Обычно затенение лучше избегать в целях сохранения чистоты кода.
Замыкание let
переменных
Вернемся к примеру с setTimeout
. let
объявление переменной создает свою область видимости для каждой итерации. Поэтому с let
нет необходимости использовать немедленно вызываемую функцию.
for (let i = 0; i < 10 ; i++) {
setTimeout(function() {console.log(i); }, 100 * i);
}
будет работать корректно:
0
1
2
3
4
5
6
7
8
9
Оператор const
const numLivesForCat = 9;
Очень похож на let
, но, как следует из название, значение таких переменных нельзя изменить. Другими словами, они имеют такие же правила видимости, как и let
, но им нельзя повторно присвоить значение.
Однако не следует полагать, что сущности, на которые эти переменные ссылаются, неизменны.
const numLivesForCat = 9;
const kitty = {
name: 'Мурз',
numLives: numLivesForCat,
}
// Ошибка
kitty = {
name: 'Бобик',
numLives: numLivesForCat
};
// все OK
kitty.name = 'Кот';
kitty.numLives--;