1. Основное использование.

В ES6 появился оператор let для объявления переменных. Его использование похоже на var, но переменные, объявленные с использованием let, действительны только внутри блока кода, где используется оператор let.

{
  let a = 10;
  var b = 1;
}
// ReferenceError: a is not defined.
a
// 1
b

В приведенном выше коде две переменные объявлены с использованием let и var внутри блока кода. Затем при попытке доступа к этим переменным вне блока кода переменная, объявленная с помощью let, приводит к ошибке, а переменная, объявленная с помощью var, возвращает правильное значение. Это показывает, что переменные, объявленные с помощью let, действительны только внутри того блока, в котором они объявлены. Оператор let хорошо подходит для использования со счетчиком в цикле for.

for (let i = 0; i < 10; i++) {
  // ...
}
// ReferenceError: i is not defined.
console.log(i);

В приведенном выше коде счетчик i действителен только внутри тела цикла for, и обращение к нему вне цикла приведет к ошибке. В следующем коде, если используется var, конечный результат будет 10.

var a = [];
for (var i = 0; i < 10; i++) {
  a[i] = function() {
    console.log(i);
  };
}
// 10
a[6]();

В этом коде переменная i объявлена ​​с именем var, которое допустимо в глобальной области видимости, в результате чего создается только одна глобальная переменная i. Каждая итерация цикла изменяет значение i, а функция внутри цикла, присвоенная массиву a, ссылается на глобальный i. Таким образом, все элементы в массиве a ссылаются на один и тот же глобальный i, что приводит к конечному выводу 10. Если используется let, объявленная переменная действительна только в пределах области действия блока, в результате чего на выходе получается 6.

var a = [];
for (let i = 0; i < 10; i++) {
  a[i] = function() {
    console.log(i);
  };
}
// 6
a[6]();

В этом коде переменная i объявлена ​​с let, создавая новую переменную i для каждой итерации цикла. Это гарантирует, что каждая итерация будет иметь свое собственное значение i, в результате чего на выходе будет 6. Механизм JavaScript запоминает значение i из предыдущей итерации при инициализации переменной для текущей итерации. Более того, цикл for имеет уникальное поведение: часть, задающая переменную цикла, действует как родительская область, а тело цикла действует как отдельная дочерняя область.

for (let i = 0; i < 3; i++) {
  let i = 'abc';
  console.log(i);
}
// abc
// abc
// abc

Правильное выполнение приведенного выше кода приведет к выводу «abc» три раза. Это указывает на то, что переменная i в области действия функции отделена от переменной цикла i и имеет свою собственную область действия.

(Мягкие навыки для программистов: amazon.com/dp/B0CF599P8J)

2. Отсутствие переменного подъема.

Оператор var демонстрирует явление, называемое «поднятием переменной», когда переменную можно использовать до ее объявления, при этом ее значение равно undefined. Такое поведение может показаться странным, поскольку, согласно общепринятой логике, переменные можно использовать только после их объявления.

Чтобы исправить это поведение, оператор let изменяет правила синтаксиса. Переменные, объявленные с помощью let, должны использоваться после их объявления, в противном случае будет выдана ошибка.

// Situation with 'var'
// Outputs 'undefined'
console.log(foo);
var foo = 2;
​
// Situation with 'let'
// Throws a ReferenceError
console.log(bar);
let bar = 2;

В приведенном выше коде, когда переменная foo объявляется с помощью команды var, происходит подъем переменной. Это означает, что в начале выполнения скрипта переменная foo уже существует, но у нее нет значения, в результате чего на выходе получается undefined. Однако когда переменная bar объявляется с помощью команды let, подъем переменной не происходит. Это означает, что до объявления переменная bar не существовала. Поэтому попытка использовать его до объявления приведет к ошибке.

(Мягкие навыки для программистов: amazon.com/dp/B0CF599P8J)

3. Временная мертвая зона (ВМЗ).

Пока команда let существует в области уровня блока, переменные, объявленные ею, «привязаны» к этой области и не подвержены влиянию внешних факторов.

var tmp = 123;
if (true) {
    tmp = 'abc'; // ReferenceError
    let tmp;
}

В приведенном выше коде есть глобальная переменная tmp, но в области уровня блока локальная переменная tmp объявляется с использованием let, которая привязывает ее к этой области уровня блока. Следовательно, попытка присвоить значение tmp до его объявления с помощью let приведет к ошибке. ES6 явно указывает, что если блок содержит команды let или const, то этот блок с самого начала образует закрытую область видимости для переменных, объявленных с помощью этих команд. Использование этих переменных до их объявления внутри блока приведет к ошибке.

По сути, внутри блока кода переменные, объявленные с помощью команды let, недоступны до их объявления. Эту ситуацию называют «временной мертвой зоной» (ВМЗ).

if (true) {
    // TDZ starts
    tmp = 'abc'; // ReferenceError
    console.log(tmp); // ReferenceError
​
    let tmp; // TDZ ends
    console.log(tmp); // undefined
​
    tmp = 123;
    console.log(tmp); // 123
}

В приведенном выше коде любое использование переменной tmp до ее объявления с помощью let попадает в «мертвую зону» этой переменной.

«Временная мертвая зона» также означает, что typeof больше не является полностью безопасной операцией.

typeof x; // ReferenceError
let x;

В этом коде переменная x объявляется с помощью команды let. Следовательно, любое использование x до его объявления попадает в «мертвую зону» и приведет к ошибке. Следовательно, использование typeof в таких случаях приведет к выдаче ReferenceError.

Для сравнения, если переменная вообще не была объявлена, использование typeof не приведет к ошибке.

typeof undeclared_variable // "undefined"

В этом коде undeclared_variable — это несуществующее имя переменной, а результат — "undefined". Таким образом, до появления let оператор typeof всегда был безопасным и никогда не выдавал ошибок. Это уже не так, поскольку он разработан для поощрения хороших привычек программирования, при которых переменные следует использовать только после их объявления, чтобы предотвратить неожиданное поведение.

Некоторые случаи «мертвой зоны» могут быть менее очевидными и их труднее обнаружить.

function bar(x = y, y = 2) {
    return [x, y];
}
// Throws an error
bar();

Причина ошибки при вызове функции bar выше (хотя некоторые реализации могут не выдавать ошибку) заключается в том, что значение параметра x по умолчанию присвоено другому параметру y, и на данный момент y еще не объявлен, падая в «мертвой зоне». Если бы значением по умолчанию y было x, ошибки не было бы, поскольку было бы объявлено x.

Более того, следующий код выдаст ошибку, в отличие от поведения var.

// Doesn't throw an error
var x = x;
​
// Throws an error
// ReferenceError: x is not defined
let x = x;

Ошибки в приведенном выше коде связаны с временной мертвой зоной. При объявлении переменных с помощью let любая попытка доступа к переменной до завершения ее объявления приведет к ошибке. В этих примерах попытка доступа к значению x до выполнения его оператора объявления приводит к ошибке «x не определен».

ES6 ввел временную мертвую зону и устранил подъем переменных для операторов let и const, главным образом, для уменьшения ошибок во время выполнения и предотвращения использования переменных до того, как они были правильно объявлены. Подводя итог, можно сказать, что суть временной мертвой зоны заключается в том, что переменные существуют в своей области видимости, как только она входит в область, но к ним нельзя получить доступ, пока не встретится строка их объявления.

(Мягкие навыки для программистов: amazon.com/dp/B0CF599P8J)

4. Дублирование деклараций не допускается.

let не позволяет объявлять одну и ту же переменную более одного раза в одной области.

// Throws an error
function example() {
    let a = 10;
    var a = 1;
}
​
// Throws an error
function example() {
    let a = 10;
    let a = 1;
}

Следовательно, недопустимо повторно объявлять параметр внутри одной и той же функции.

function func(arg) {
    // Throws an error
    let arg;
}
​
function func(arg) {
    {
        // No error
        let arg;
    }
}

(Мягкие навыки для программистов: amazon.com/dp/B0CF599P8J)