Artigo original: What is the Temporal Dead Zone (TDZ) in JavaScript?

Eu imagino que Zona Morta Temporal (em inglês, Temporal Dead Zone ou TDZ) pareça uma expressão saída de uma série de ficção científica. No entanto, é útil entender o significado dos termos e conceitos com os quais você trabalha diariamente (ou sobre os quais deseja aprender a respeito).

Prepare-se, então, porque as coisas ficarão bastante complicadas.

Você sabia que, em JavaScript, podemos adicionar chaves – { } – para adicionar um nível a mais de escopo onde quisermos?

Quem quiser, pode, tranquilamente, fazer o seguinte:

{ { { { { { var loucuraPura = true } } } } } }
Eu espero que você jamais veja isso em produção!

Incluí esse detalhe para garantir que os exemplos a seguir façam sentido (já que eu não queria dar como certo que todos sabem a respeito disso).

Antes da ES6, não havia outra maneira de declarar variáveis que não fosse com a palavra-chave var. A ES6, porém, nos trouxe let e const.

As declarações de let e const têm escopo de bloco, o que significa que são acessíveis apenas dentro das chaves {} que as cercam. var, por outro lado, não tem essa restrição.

Aqui temos um exemplo:

let idadeDoBebe = 1;
let deAniversario = true;

if (deAniversario) {
	let idadeDoBebe = 2; 
}

console.log(idadeDoBebe); // O resultado é 1. Por quê?
São duas variáveis exclusivas com valores diferentes.

O que ocorreu acima tem a ver com a nova declaração de idadeDoBebe como sendo 2, disponível apenas dentro do bloco da instrução if. Para além da instrução, é usada a idadeDoBebe de fora do bloco. Consegue perceber que são duas variáveis diferentes, apesar do mesmo nome?

No entanto, a declaração com var não tem escopo de bloco:

var idadeDoBebe = 1;
var deAniversario = true;

if (deAniversario) {
	var idadeDoBebe = 2; 
}

console.log(idadeDoBebe); // Aqui, o resultado é 2
Aqui temos uma variável com o valor declarado duas vezes.

A diferença destacável aqui entre let / const e var está no fato de que, se você acessar var antes de ela ser declarada, ela tem o valor undefined. Se, no entanto, você fizer o mesmo para let e const, terá o chamado ReferenceError.

console.log(varNumero); // undefined
console.log(letNumero); // Não aparece no console e é lançado um erro dizendo que ReferenceError letNumero não é definido

var varNumero = 1;
let letNumero = 1;

Esse erro é lançado devido à Zona Morta Temporal.

A Zona Morta Temporal explicada

O que é a Zona Morta Temporal? O termo serve para descrever o estado onde variáveis não são alcançáveis. Elas estão no escopo, mas não são declaradas.

As variáveis let e const existem na Zona Morta Temporal desde o início do escopo que as cerca até serem declaradas.

É possível dizer que as variáveis existem na Zona Morta Temporal do local ao qual elas estão vinculadas (quando a variável fica associada ao escopo em que está) até ser declarada (quando é reservado um nome na memória para aquela variável).

{
 	// Esta é a zona morta temporal para a variável idade!
    // Esta é a zona morta temporal para a variável idade!
    // Esta é a zona morta temporal para a variável idade!
    // Esta é a zona morta temporal para a variável idade!
	let idade = 25; // Pronto, chegamos ao fim da zona morta!
	console.log(idade);
}
A Zona Morta Temporal capturada e registrada.

Como podemos ver acima, se eu acessasse a variável idade antes de sua declaração, teríamos um ReferenceError. Isso ocorre por causa da Zona Morta Temporal.

As variáveis com var, porém, não passam por isso. var é apenas inicializada por padrão como undefined, diferentemente das outras declarações.

Qual é a diferença entre declarar e inicializar?

Aqui temos um exemplo de como declarar e de como inicializar uma variável.

function exemploDeEscopo() {

    let idade; // 1
    idade = 20; // 2
    let maos = 2; // 3
}
Declaração e inicialização de uma variável.

Declarar uma variável significa reservar para ela um nome na memória no escopo atual. Isso é exemplificado pelo comentário com o número 1.

Inicializar uma variável é definir o valor dessa variável. Isso é exemplificado pelo comentário com o número 2.

Também é possível fazer as duas coisas em uma única linha. Isso é exemplificado pelo comentário com o número 3.

Apenas para reforçar: as variáveis let e const existem na Zona Morta Temporal do início do escopo que as envolve até que elas sejam declaradas.

Assim, a partir do treco de código acima, onde está a Zona Morta Temporal para a variável idade? A variável maos tem uma Zona Morta Temporal? Se tem, onde é o seu início e o seu final?

Resposta: tanto a variável maos quanto a variável idade entram na Zona Morta Temporal. A Zona para maos termina quando ela é declarada, na mesma linha em que ela é definida como 2. No caso de idade, a Zona Morta termina quando ela é declarada e quando seu nome é reservado na memória (na etapa 2, onde eu comentei).

Por que a Zona Morta Temporal é criada no local em questão?

Vamos voltar ao nosso primeiro exemplo:

{
    // Esta é a zona morta temporal para a variável idade!
    // Esta é a zona morta temporal para a variável idade!
    // Esta é a zona morta temporal para a variável idade!
    // Esta é a zona morta temporal para a variável idade!
    let idade = 25; // Pronto, chegamos ao fim da zona morta!
    console.log(idade);
}

Se adicionarmos um console.log dentro da Zona Morta Temporal, veremos este erro:

image-5

Por que a zona morta existe entre o início do escopo e a declaração da variável? Qual a razão específica para isso?

Isso tem a ver com o hoisting (em português, algo como "içamento").

A engine do JS que faz a análise do código e o executa tem duas etapas para realizar:

  1. Analisar o código na Árvore de Sintaxe Abstrata/byte code executável, e
  2. Execução em tempo de execução (runtime).

O passo 1 é onde ocorre o hoisting. Isso é feito pela engine do JS. Essencialmente, movemos todas as nossas declarações de variáveis para o início do escopo. Aqui temos um exemplo:

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

Para ser claro, essas variáveis não estão sendo movidas fisicamente dentro do código. O resultado, contudo, seria funcionalmente idêntico ao que vemos abaixo:

var variavelComHoist;

console.log(variavelComHoist); // undefined
contador = 1;

A única diferença entre const e let está no fato de que, após o hoisting, seus valores não tem undefined como padrão.

Apenas para provar que let e const também passam pelo hoisting, aqui temos um exemplo:

{
    // As duas variáveis abaixo passarão por hoisting na parte superior do seu escopo!
	console.log(typeof semSentidoQueNaoExiste); // O resultado é undefined
	console.log(typeof nome); // Lança um erro, não é possível acessar 'nome' antes de sua inicialização

	let nome = "Kealan";
}

O trecho acima prova que let passa pelo hoisting acima de onde está declarada, como a engine alerta para isso. Ela sabe que nome existe (está declarada), mas não podemos acessá-la antes de a inicializarmos.

Se ajudar a se lembrar, pense o seguinte.

Quando as variáveis passam por hoisting, var é inicializada como undefined por padrão no processo de hoisting. let e const também passam por hoisting, mas não recebe o valor de undefined quando passa pelo hoisting.

Esse é o único motivo para termos a Zona Morta Temporal. Por isso ela acontece com let e const, mas não com var.

Mais exemplos da Zona Morta Temporal

A Zona Morta Temporal também pode ser criada para parâmetros de função padrão. Algo assim:

function criarZonaMorta(a=b, b) {
}

criarZonaMorta(undefined, 1); 

Isso lançará um ReferenceError, pois a avaliação da variável a tenta acessar a variável b antes de ela ser analisada pela engine do JS. Os argumentos da função estão todos dentro da Zona Morta Temporal até serem analisadas.

Mesmo algo simples como let testeDaZona = testeDaZona; retornaria um erro em função da Zona Morta Temporal. Porém, as variáveis com var aqui apenas criariam testeDaZona e a definiriam como undefined.

Falta apenas um exemplo final e bem mais avançado, de Erik Arvindson (que está envolvido com a evolução e a manutenção das especificações do ECMAScript):

let a = f(); // 1
const b = 2;
function f() { return b; } // 2, b está na Zona Morta Temporal
Um último exemplo de Zona Morta Temporal.

Você pode seguir os números comentados.

Na primeira linha, chamamos a função f. Em seguida, tentamos acessar a variável b (que lança um ReferenceError, pois b está na Zona Morta Temporal).

Por que temos a Zona Morta Temporal?

O Dr. Alex Rauschmayer fez um artigo excelente (texto em inglês) sobre o motivo da existência da Zona Morta Temporal. O principal motivo é o fato de ela nos ajudar a capturar erros.

Tentar acessar uma variável antes de ela ser declarada é o jeito errado de se fazer e não deveria ser possível.

Essa também é uma semântica mais esperada e racional para const (pois const também passa por hoisting – o que aconteceria se um programador tentasse usar uma variável declarada com const antes de ela ser declarada em tempo de execução? Que variável deveria estar ali no momento do hoisting?). Foi a melhor abordagem encontrada pela equipe de especificações do ECMAScript.

Como evitar os problemas causados pela Zona Morta Temporal

Colocado de um modo relativamente simples, lembre-se sempre de definir as variáveis declaradas por  let e const na parte superior do escopo em que se encontram.