¿Qué es la zona muerta temporal (TDZ) en JavaScript?

Sé que Temporal Dead Zone suena como una frase de ciencia ficción. Pero es útil comprender qué significan los términos y conceptos con los que trabaja todos los días (o sobre los que quiere aprender).

Abróchate el cinturón, porque esto se complica.

¿Sabes que en JavaScript podemos agregar { }para agregar un nivel de alcance donde queramos?

Entonces siempre podríamos hacer lo siguiente:

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

He incluido este detalle para asegurarme de que los próximos ejemplos tengan sentido (ya que no quería asumir que todos lo sabían).

Antes de ES6 no había otra forma de declarar variables que no fuera var. Pero ES6 nos trajo lety const.

lety las constdeclaraciones tienen un alcance de bloque, lo que significa que solo son accesibles dentro de las que las {}rodean. var, por otro lado, no tiene esta restricción.

He aquí un ejemplo:

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

Lo anterior ha ocurrido porque la re-declaración de babyAgea 2 solo está disponible dentro del ifbloque. Más allá de eso, babyAgese usa el primero . ¿Puedes ver que son dos variables diferentes?

Por el contrario, la vardeclaración no tiene alcance de bloque:

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

La última diferencia sobresaliente entre let/ consty vares que si accede varantes de que se declare, no está definido. Pero si haces lo mismo con lety const, lanzan un 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;

Lanzan el error todo debido a la Zona Muerta Temporal.

Explicación de la zona muerta temporal

Esto es lo que es la TDZ: el término para describir el estado donde las variables son inalcanzables. Están dentro del alcance, pero no se declaran.

El letyconstlas variables existen en la TDZ desde el inicio de su ámbito adjunto hasta que se declaran.

También podría decir que las variables existen en la TDZ desde el lugar donde se vinculan (cuando la variable se vincula al ámbito que está dentro) hasta que se declara (cuando se reserva un nombre en la memoria para esa 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! // This is the temporal dead zone for the age variable! let age = 25; // Whew, we got there! No more TDZ console.log(age); }

Puede ver arriba que si accedía a la variable age antes de su declaración, arrojaría un ReferenceError. Debido a la TDZ.

Pero varno haré eso. varse inicializa por defecto a undefineddiferencia de la otra declaración.

¿Cuál es la diferencia entre declarar e inicializar?

A continuación, se muestra un ejemplo de cómo declarar una variable e inicializarla.

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

Declarar una variable significa que reservamos el nombre en la memoria en el ámbito actual. Eso está etiquetado como 1 en los comentarios.

Inicializar una variable es establecer el valor de la variable. Eso está etiquetado como 2 en los comentarios.

O siempre puedes hacer ambas cosas en una línea. Eso está etiquetado como 3 en los comentarios.

Solo para repetirme de nuevo: el letyconstlas variables existen en la TDZ desde el inicio de su ámbito adjunto hasta que se declaran.

Entonces, a partir del fragmento de código anterior, ¿dónde está la TDZ age? Además, ¿ handstiene TDZ? Si es así, ¿dónde está el inicio y el final de la TDZ para las manos?

Comprueba tu respuesta Las variables de manos y edad entran en la TDZ.

La TDZ para manos finaliza cuando se declara, la misma línea que se establece en 2.

El TZ para la edad termina cuando se declara y el nombre se reserva en la memoria (en el paso 2, donde comenté).

¿Por qué se crea la TDZ cuando se crea?

Volvamos a nuestro primer ejemplo:

{ // 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); }

Si agregamos un console.logdentro de la TDZ, verá este error:

¿Por qué existe TDZ entre la parte superior del alcance y la declaración de variable? ¿Cuál es la razón específica de eso?

Es por izar.

El motor JS que analiza y ejecuta su código tiene 2 pasos para realizar:

  1. Análisis del código en un árbol de sintaxis abstracta / código de byte ejecutable, y
  2. Ejecución en tiempo de ejecución.

El paso 1 es donde ocurre la elevación, y esto lo realiza el motor JS. Básicamente, moverá todas sus declaraciones de variables a la parte superior de su alcance. Entonces un ejemplo sería:

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

Para ser claros, estas variables no se mueven físicamente en el código. Pero, el resultado sería funcionalmente idéntico al siguiente:

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

La única diferencia entre consty letes que cuando se levantan, sus valores no se vuelven predeterminados undefined.

Solo para probar lety consttambién izar, aquí hay un ejemplo:

{ // 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"; }

El fragmento anterior es una prueba de que letestá claramente colocado por encima de donde se declaró, ya que el motor nos alerta del hecho. Sabe que nameexiste (está declarado), pero no podemos acceder a él antes de que se inicialice.

Si te ayuda a recordar, piénsalo así.

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.