let¶
В Javascript ключевое слово var
относится к функциональной области видимости. Этим javascript отличается от многих языков программирования (C# / Java и т. д.), в которых переменные имеют блочную область видимости. Если представить блочную видимость в JavaScript, то в примере ниже мы ожидаем, что в консоль попадет 123
, однако вместо этого будет выведено 456
:
var foo = 123;
if (true) {
var foo = 456;
}
console.log(foo); // 456
Так происходит, потому что {
не создает новую область видимости переменных. Переменная foo
, которая находится внутри if, ведет себя так же, как если бы она была объявлена вне его. Это поведение часто является источником ошибок в JavaScript. По этой причине TypeScript (и ES6) добавили ключевое слово let
, которое позволяет нам объявлять переменные с настоящей блочной областью видимости.
Таким образом, если вы используете ключевое слово let
вместо var
, вы получите по настоящему уникальный элемент, который не будет взаимодействовать с областями видимости, которые располагаются вне его области видимости. Ниже мы переписали предыдущий пример, используя let
:
let foo = 123;
if (true) {
let foo = 456;
}
console.log(foo); // 123
Еще один пример, где let
может защитить вас от ошибок.
var index = 0;
var array = [1, 2, 3];
for (let index = 0; index < array.length; index++) {
console.log(array[index]);
}
console.log(index); // 0
Честно говоря, мы считаем, что лучше использовать let
, когда это возможно, так как это приводит к меньшим сюрпризам для новых и существующих разработчиков.
Функции создают новую область видимости¶
Поскольку мы упоминали об этом, мы хотели бы продемонстрировать, что функции создают новую область видимости переменных в JavaScript:
var foo = 123;
function test() {
var foo = 456;
}
test();
console.log(foo); // 123
Это ведет себя так, как вы ожидаете. Без этого было бы очень сложно писать код на JavaScript.
Сгенерированный JS¶
JS код, генерируемый TypeScript, является простым переименованием переменной let
, если подобное имя уже существует в окружающей области видимости. Например. следующий код генерируется как есть с простой заменой var
на let
:
if (true) {
let foo = 123;
}
// становится //
if (true) {
var foo = 123;
}
Однако, если имя уже используется в окружающей области видимости, то typescript сгенерирует следующий код:
var foo = '123';
if (true) {
let foo = 123;
}
// становится //
var foo = '123';
if (true) {
var foo_1 = 123; // просто переименовываем
}
Switch¶
Мы можем обернуть тело case
, используя {}
для переиспользования имени переменной в других case
выражениях, как показано ниже:
switch (name) {
case 'x': {
let x = 5;
// ...
break;
}
case 'y': {
let x = 10;
// ...
break;
}
}
let в замыканиях¶
Самый частый вопрос на собеседовани - какой будет результат при исполнении следующего кода:
var funcs = [];
// создаем список функций
for (var i = 0; i < 3; i++) {
funcs.push(function () {
console.log(i);
});
}
// вызываем их
for (var j = 0; j < 3; j++) {
funcs[j]();
}
Начинающие разработчики часто говорят, что результатом будет 0,1,2
. Однако очень сильно удивляются, когда узнают, что этот ответ неправильный, а результатом исполнения функции будет 3
во всех трех функциях. Так происходит, потому что все эти функции выводят значение переменной i
из окружающей области видимости, в момент вызова этих функций она равняется 3.
Одним из возможных вариантов исправления этой ошибки является создание функции для каждой итерации цикла. Как мы узнали ранее, мы можем создать новую область видимости с помощью немедленно вызывающейся функции (паттерн IIFE (function() { /* body */ })();
):
var funcs = [];
// создаем список функций
for (var i = 0; i < 3; i++) {
(function () {
var local = i;
funcs.push(function () {
console.log(local);
});
})();
}
// вызываем их
for (var j = 0; j < 3; j++) {
funcs[j]();
}
В этом примере функция замыкает (поэтому ее и называют замыкание
) локальные переменные (по соглашению называемые local
) и использует их вместо i
, которая используется в цикле.
Обратите внимание, что замыкания влияют на производительность (но они нам нужны для сохранения окружающего состояния).
В ES6 ключевое слово let
в цикле будет иметь такое же поведение, как и в примере ранее:
var funcs = [];
// создаем список инструкций
for (let i = 0; i < 3; i++) {
// используем let
funcs.push(function () {
console.log(i);
});
}
// вызываем
for (var j = 0; j < 3; j++) {
funcs[j]();
}
Использование let
вместо var
создает переменную i
, уникальную для каждой итерации цикла.
Резюме¶
let
- нереально полезный инструмент, который рекомендуется использовать как можно чаще. Он может существено повысить читабельность и предсказуемость вашего кода и снизить количество допускаемых ошибок.