Що таке тимчасова мертва зона (TDZ) у JavaScript?

Я знаю, що Temporal Dead Zone звучить як науково-фантастична фраза. Але корисно зрозуміти, що означають терміни та поняття, з якими ви працюєте щодня (або хочете дізнатись про них).

Прив’яжіть, тому що це ускладнюється.

Чи знаєте ви, що в JavaScript ми можемо додати, { }щоб додати рівень сфери, де завгодно?

Тому ми завжди могли зробити наступне:

{ { { { { { var madness = true } } } } } }

Я включив цю деталь, щоб переконатися, що майбутні приклади мають сенс (оскільки я не хотів припускати, що всі це знали).

До ES6 не було іншого способу оголосити змінні, крім var. Але ES6 приніс нам letі const.

letі constдекларації обидва мають блок-область, що означає, що вони доступні лише в {}оточуючих їх. var, з іншого боку, не має цього обмеження.

Ось приклад:

let babyAge = 1; let isBirthday = true; if (isBirthday) { let babyAge = 2; } console.log(babyAge); // Hmmmm. This prints 1

Вищезазначене сталося, оскільки повторне оголошення babyAgeдо 2 доступне лише всередині ifблоку. Крім цього, використовується перший babyAge. Чи бачите ви, що це дві різні змінні?

На відміну від цього, varдекларація не має блокової області:

var babyAge = 1; var isBirthday = true; if (isBirthday) { var babyAge = 2; } console.log(babyAge); // Ah! This prints 2

Остаточна помітна різниця між let/ constта varполягає в тому, що якщо ви отримуєте доступ varдо того, як він був оголошений, він не визначений. Але якщо ви зробите те саме для letі const, вони кинуть ReferenceError.

console.log(varNumber); // undefined console.log(letNumber); // Doesn't log, as it throws a ReferenceError letNumber is not defined var varNumber = 1; let letNumber = 1;

Вони викидають помилку все через тимчасову мертву зону.

Пояснення тимчасової мертвої зони

Ось що таке TDZ: термін, що описує стан, коли змінні недоступні. Вони входять у сферу дії, але не оголошуються.

letіconstзмінні існують у TDZ від початку їхньої обширної області дії до моменту їх оголошення.

Ви також можете сказати, що змінні існують у TDZ з місця, де вони прив'язуються (коли змінна прив'язується до області дії, яка знаходиться всередині), доки вона не буде оголошена (коли ім'я зарезервовано в пам'яті для цієї змінної).

{ // This is the temporal dead zone for the age variable! // This is the temporal dead zone for the age variable! // This is the temporal dead zone for the age variable! // This is the temporal dead zone for the age variable! let age = 25; // Whew, we got there! No more TDZ console.log(age); }

Ви можете бачити вище, що якби я звернувся до змінної віку раніше, ніж її оголошення, це викинуло б ReferenceError. Через TDZ.

Але varне буде цього робити. varпросто ініціалізується за замовчуванням, на undefinedвідміну від іншого оголошення.

Яка різниця між декларуванням та ініціалізацією?

Ось приклад оголошення змінної та ініціалізації змінної.

function scopeExample() { let age; // 1 age = 20; // 2 let hands = 2; // 3 }

Оголошення змінної означає, що ми зберігаємо ім'я в пам'яті на поточному рівні. Це позначено в коментарях 1.

Ініціалізація змінної - це встановлення значення змінної. Це позначено в коментарях 2.

Або ви завжди можете робити обидва на одному рядку. Це позначено в коментарях 3.

Просто, щоб повторити ще раз: letіconstзмінні існують у TDZ від початку їхньої обширної області дії до моменту їх оголошення.

Тож із наведеного вище фрагмента коду, де TDZ age? Крім того, чи handsє TDZ? Якщо так, де початок і кінець TDZ для рук?

Перевірте свою відповідь Змінні руки та вік вводять TDZ.

TDZ для рук закінчується, коли його оголошують, той самий рядок, який він встановлює на 2.

TZ для віку закінчується, коли його оголошують, а ім'я зарезервовано в пам'яті (на кроці 2, де я коментував).

Чому TDZ створюється, коли він є?

Повернемось до нашого першого прикладу:

{ // This is the temporal dead zone for the age variable! // This is the temporal dead zone for the age variable! // This is the temporal dead zone for the age variable! // This is the temporal dead zone for the age variable! let age = 25; // Whew, we got there! No more TDZ console.log(age); }

Якщо ми додамо console.logвсередині TDZ, ви побачите цю помилку:

Чому TDZ існує між вершиною області дії та оголошенням змінної? Яка конкретна причина цього?

Це через підняття.

Механізм JS, який аналізує та виконує ваш код, має виконати 2 кроки:

  1. Синтаксичний аналіз коду в абстрактному дереві синтаксису / виконуваному байтовому коді та
  2. Виконання часу виконання.

Крок 1 - це місце, де відбувається підняття, і це робиться двигуном JS. Це по суті перемістить усі ваші декларації змінних до початку їхньої області дії. Отже, прикладом може бути:

console.log(hoistedVariable); // undefined var hoistedVariable = 1;

Щоб бути зрозумілим, ці змінні фізично не рухаються в коді. Але результат буде функціонально ідентичним наведеному нижче:

var hoistedVariable; console.log(hoistedVariable); // undefined counter = 1;

Єдина відмінність між constі letполягає в тому, що коли їх піднімають, їх значення не отримують за замовчуванням undefined.

Щоб довести, letа constтакож підняти, ось приклад:

{ // Both the below variables will be hoisted to the top of their scope! console.log(typeof nonsenseThatDoesntExist); // Prints undefined console.log(typeof name); // Throws an error, cannot access 'name' before initialization let name = "Kealan"; }

Наведений фрагмент є доказом того, що letвін чітко піднятий вище там, де він був оголошений, оскільки двигун попереджає нас про факт. Він знає, що nameіснує (він оголошений), але ми не можемо отримати до нього доступ до його ініціалізації.

Якщо це допомагає вам пам’ятати, думайте про це так.

When variables get hoisted, var gets undefined initialized to its value by default in the process of hoisting. let and const also get hoisted, but don't get set to undefined when they get hoisted.

And that's the sole reason we have the TDZ. Which is why it happens with let and const but not var.

More examples of the TDZ

The TDZ can also be created for default function parameters. So something like this:

function createTDZ(a=b, b) { } createTDZ(undefined, 1); 

throws a ReferenceError, because the evaluation of variable a tries to access variable b before it has been parsed by the JS engine. The function arguments are all inside the TDZ until they are parsed.

Even something as simple as let tdzTest = tdzTest; would throw an error due to the TDZ. But var here would just create tdzTest and set it to undefined.

There's one more final and fairly advanced example from Erik Arvindson (who's involved in evolving and maintaining the ECMAScript spec):

let a = f(); // 1 const b = 2; function f() { return b; } // 2, b is in the TDZ 

You can follow the commented numbers.

In the first line we call the f function, and then try to access the b variable (which throws a ReferenceError because b is in the TDZ).

Why do we have the TDZ?

Dr Alex Rauschmayer has an excellent post on why the TDZ exists, and the main reason is this:

It helps us catch errors.

To try and access a variable before it is declared is the wrong way round, and shouldn't be possible.

It also gives more expected and rational semantics for const (because const is hoisted, what happens if a programmer tries to use it before it is declared at runtime? What variable should it hold at the point when it gets hoisted?), and was the best approach decided by the ECMAScript spec team.

How to avoid the issues the TDZ causes

Relatively simply, always make sure you define your lets and consts at the top of your scope.