Lo sé, Zona Muerta Temporal suena como a una frase de Ciencia Ficción, pero es muy útil para entender qué significan términos y conceptos con los que trabajas (o quieres aprender) día a día.

Agárrate, porque esto se complica.

¿Eres consciente que en JavaScript podemos añadir { } para sumar un nivel al scope donde queramos?

Por ejemplo, siempre podemos hacer lo siguiente:

{ { { { { { var madness = true } } } } } }
¡Esperemos que nunca veas algo similar en producción!

He incluido este detalle para estar seguro de que los siguientes ejemplos tengan sentido (No he querido asumir que todo el mundo lo sabe de antemano).

Antes de ES6 no había otra forma de declarar variables que usar var pero ES6 nos trajo las opciones de let y const.

Las declaraciones let y const son ambas block-scoped, que significa que solo son accesibles dentro de las llaves { } que les rodean. La declaración var, por otro lado, no tiene esa restricción.

Aquí hay un ejemplo:

let babyAge = 1;
let isBirthday = true;

if (isBirthday) {
	let babyAge = 2; 
}

console.log(babyAge); // Hmmmm. Esto imprime un 1
2 variables únicas, con diferentes valores.

Lo anterior ha ocurrido porque la redeclaración de babyAge a 2 se encuentra solo disponible dentro del bloque if . Más allá, se usa el primer valor de babyAge . ¿Puedes ver que son 2 variables diferentes?

En contraste, la declaración var no está limitada al scope:

var babyAge = 1;
var isBirthday = true;

if (isBirthday) {
	var babyAge = 2; 
}

console.log(babyAge); // Ah! Esto imprime 2
One variable with it's value re-declared. 

La última diferencia destacada entre let / const y var  es que, si tu accedes a var  antes de que sea declarada, su valor es indefinido, pero si haces lo mismo para let  y const, te saldrá un ReferenceError.

console.log(varNumber); // undefined
console.log(letNumber); // No se registra, lanza un "ReferenceError letNumber is not defined"

var varNumber = 1;
let letNumber = 1;

Lanzan el error debido a lo que se conoce como Zona Muerta Temporal.

La Zona Muerta Temporal, explicada.

Esto es lo que es la TDZ: El término describe el estado donde las variables son inaccesibles. Se encuentran en el scope, pero no han sido declaradas todavía.

Las variables let y const existen en la TDZ desde el inicio de su ámbito de aplicación, su scope, hasta que son declaradas.

También se puede decir que las variables existen en la TDZ desde que se vinculan (cuando la variable se vincula al ámbito o scope en el que está) hasta que se declara (cuando se reserva un nombre en memoria para esa variable).

{
 	// ¡Esta es la Zona muerta temporal para la variable age!
	// ¡Esta es la Zona muerta temporal para la variable age!
	// ¡Esta es la Zona muerta temporal para la variable age!
 	// ¡Esta es la Zona muerta temporal para la variable age!
	let age = 25; // Cuando llegamos aquí, se acabó TDZ.
	console.log(age);
}
La zona muerta temporal capturada y catalogada.

Puedes ver arriba que, si se quiere acceder a la variable age, antes de su declaración, nos arrojará un ReferenceError. Por la TDZ.

Pero var no hace eso, var es inicializada como undefined a diferencia de las otras declaraciones, let y const.

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

Aquí hay un ejemplo de declaración e inicialización de una variable

function scopeExample() {

    let age; // 1
    age = 20; // 2
    let hands = 2; // 3
}
Declaración vs inicialización.

Declarar una variable significa que se reserva el nombre en memoria en el scope actual. En el ejemplo, el etiquetado como 1 en los comentarios.

Inicializar una variable es establecer el valor de la variable. En este caso, etiquetado como 2 en los comentarios.

O siempre se puede hacer ambas tareas en una sola línea. Es la etiquetada como 3 en comentarios.

Repitámoslo otra vez: Las variables let y const existen en la TDZ desde el inicio de su scope hasta que son declaradas.

Entonces, a partir del fragmento de código anterior, ¿dónde se sitúa la TDZ para age? Además, ¿La variable hands tiene TDZ? si es así, ¿Dónde está el inicio y final de la TDZ de hands ? Intenta resolverlo antes de continuar.

Vamos a comprueba tus respuestas:

Tanto hands como age están dentro de la TDZ.

La TDZ para hands finaliza cuando es declarada, la misma línea en que se le pone valor 2.

La TDZ para age finaliza cuando es declarada y el nombre reservado en memoria (En el paso 2).

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

Volvamos a nuestro primer ejemplo:

{
    // ¡Esta es la Zona Muerta Temporal para la variable age!
    // ¡Esta es la Zona Muerta Temporal para la variable age!
    // ¡Esta es la Zona Muerta Temporal para la variable age!
    // ¡Esta es la Zona Muerta Temporal para la variable age!
    let age = 25; // Cuando llegamos aquí, se acabó la TDZ
    console.log(age);
}

Si añadimos un console.log dentro de la TDZ veremos el siguiente error:

image-5

¿Por qué existe la TDZ entre en inicio del scope y la declaración de la variable? ¿Cuál es la razón específica para ello?

Es por el hoisting (elevación)

El motor de JS que está analizando y ejecutando tu código tiene 2 pasos que realizar:

  1. Analizar el código en el Arbol de Sintaxis Abstracto/código de bytes ejecutable, y
  2. Ejecución en tiempo real.

En el paso uno es cuando sucede el hoisting, realizado por el motor de JS. Esencialmente, consiste en elevar todas las variables al comienzo del scope. Un ejemplo será como sigue:

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

Para ser claro, estas variables no se mueven físicamente en el código, pero el resultado funcionalmente es idéntico a lo siguiente:

var hoistedVariable;

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

La única diferencia con const y let es que, cuando son elevadas, sus valores no toman el valor por defecto undefined.

Para probar que const y let también se elevan, veamos el ejemplo:

{
    // ¡Las 2 variables siguientes van a ser elevadas al inicio del scope!
	console.log(typeof variableQueNoExiste); // Imprime undefined
	console.log(typeof name); // Devuelve error, no puede acceder a 'name' antes de inicializarla

	let name = "Kealan";
}

El fragmento anterior es la prueba de que let es claramente elevada por encima de donde fue declarada, ya que el motor nos avisa de este hecho. Se sabe que name existe (Es declarada), pero no podemos acceder a ella antes de su inicialización.

Si te ayuda a recordarlo, pienso en ello como lo siguiente:

Cuando las variables son elevadas, var obtiene el valor de inicialización undefined por defecto en el proceso de hoisting o elevación. Por su parte let y const también son elevadas, pero no son puestas con el valor undefined en el proceso.

Y este es el único motivo por el que tenemos TDZ. Por eso ocurre con let y const pero no con var.

Más ejemplos de TDZ

La TDZ también fue creada para los parámetros por defecto de las funciones. Luego, algo como lo siguiente:

function createTDZ(a=b, b) {
}

createTDZ(undefined, 1); 

Nos lanza un ReferenceError porque la evaluación de la variable a intenta el acceso a la variable b antes que haya sido analizada por el motor de JS. Los argumentos de la función se encuentran todos dentro de la TDZ hasta que son analizados.

Incluso algo tan sencillo como let tdzTest = tdzTest; deberá lanzar un error debido a la TDZ. En este caso, var creará justo tdzTest  y la pondra a undefined.

Un último ejemplo, bastante avanzado, de Erik Arvindson (el cual está involucrado en la evolución y mantenimiento de las especificaciones de ECMAScript):

let a = f(); // 1
const b = 2;
function f() { return b; } // 2, b está en la TDZ
Un último ejemplo.

Puedes seguir los números comentados.

En la primera línea llamamos a la función f, la cual trata de acceder a la variable b , la cual lanzará un ReferenceError ya que b se encuentra en la TDZ.

¿Por qué tenemos TDZ?

El Dr. Alex Rauschmayer tiene un excelente post explicando por qué existe la TDZ y la principal razón que esgrime es la siguiente:

Nos ayuda a atrapar errores.

Intentar acceder a una variable antes de declararla es el camino equivocado, y no debería ser posible.

También da una semántica más esperada y racional para const ya que const es elevada (hoisted), porque ¿qué pasa si un programador intenta usarlo antes de que sea declarada en tiempo de ejecución? ¿Qué variable debería contener en el momento en el que se eleva? Fue el mejor enfoque decidido por el equipo de especificaciones de ECMAScript.

Cómo evitar los problemas que provoca la TDZ

Es relativamente sencillo, siempre asegúrate de definir tus lets y consts en la parte superior del scope.

Traducido del artículo de Kealan Parr - What is the Temporal Dead Zone (TDZ) in JavaScript?