Перейти к содержанию

let

В Javascript ключевое слово var относится к функциональной области видимости. Этим javascript отличается от многих языков программирования (C# / Java и т. д.), в которых переменные имеют блочную область видимости. Если представить блочную видимость в JavaScript, то в примере ниже мы ожидаем, что в консоль попадет 123, однако вместо этого будет выведено 456:

1
2
3
4
5
var foo = 123;
if (true) {
    var foo = 456;
}
console.log(foo); // 456

Так происходит, потому что { не создает новую область видимости переменных. Переменная foo, которая находится внутри if, ведет себя так же, как если бы она была объявлена вне его. Это поведение часто является источником ошибок в JavaScript. По этой причине TypeScript (и ES6) добавили ключевое слово let, которое позволяет нам объявлять переменные с настоящей блочной областью видимости.

Таким образом, если вы используете ключевое слово let вместо var, вы получите по настоящему уникальный элемент, который не будет взаимодействовать с областями видимости, которые располагаются вне его области видимости. Ниже мы переписали предыдущий пример, используя let:

1
2
3
4
5
let foo = 123;
if (true) {
    let foo = 456;
}
console.log(foo); // 123

Еще один пример, где let может защитить вас от ошибок.

1
2
3
4
5
6
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:

1
2
3
4
5
6
var foo = 123;
function test() {
    var foo = 456;
}
test();
console.log(foo); // 123

Это ведет себя так, как вы ожидаете. Без этого было бы очень сложно писать код на JavaScript.

Сгенерированный JS

JS код, генерируемый TypeScript, является простым переименованием переменной let, если подобное имя уже существует в окружающей области видимости. Например. следующий код генерируется как есть с простой заменой var на let:

1
2
3
4
5
6
7
8
9
if (true) {
    let foo = 123;
}

// становится //

if (true) {
    var foo = 123;
}

Однако, если имя уже используется в окружающей области видимости, то typescript сгенерирует следующий код:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
var foo = '123';
if (true) {
    let foo = 123;
}

// становится //

var foo = '123';
if (true) {
    var foo_1 = 123; // просто переименовываем
}

Switch

Мы можем обернуть тело case, используя {} для переиспользования имени переменной в других case выражениях, как показано ниже:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
switch (name) {
    case 'x': {
        let x = 5;
        // ...
        break;
    }
    case 'y': {
        let x = 10;
        // ...
        break;
    }
}

let в замыканиях

Самый частый вопрос на собеседовани - какой будет результат при исполнении следующего кода:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
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 */ })();):

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
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 в цикле будет иметь такое же поведение, как и в примере ранее:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
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 - нереально полезный инструмент, который рекомендуется использовать как можно чаще. Он может существено повысить читабельность и предсказуемость вашего кода и снизить количество допускаемых ошибок.

Комментарии