TypeScript. Объявление переменных

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--;