Hoisting em profundidade
Esta aula explora o mecanismo de hoisting em JavaScript, detalhando como declarações com var, let, const e funções são içadas, a Temporal Dead Zone (TDZ) e exemplos práticos para evitar armadilhas comuns.
Hoisting é um comportamento padrão do JavaScript onde declarações de variáveis e funções são movidas para o topo de seus escopos durante a fase de compilação, antes da execução do código. Isso significa que você pode usar uma variável ou função antes de declará-la no código fonte, mas o comportamento varia conforme o tipo de declaração. Compreender hoisting é essencial para evitar bugs sutis e escrever código previsível.
Nesta aula, vamos mergulhar fundo no hoisting de var, let, const e funções, entender a Temporal Dead Zone (TDZ) e ver exemplos que ilustram esses conceitos na prática.
Hoisting de var, let, const e funções
O hoisting ocorre de forma diferente para cada tipo de declaração. Variáveis declaradas com var são içadas e inicializadas com undefined. Isso significa que você pode acessá-las antes da declaração, mas o valor será undefined até que a atribuição ocorra. Já as declarações com let e const também são içadas, mas não são inicializadas. Elas entram na chamada Temporal Dead Zone (TDZ) desde o início do escopo até o ponto da declaração, e qualquer tentativa de acesso antes da declaração resulta em um erro ReferenceError.
Funções declaradas com a sintaxe function são completamente içadas, incluindo o corpo da função. Portanto, você pode chamá-las antes da declaração. Já as expressões de função (como const fn = function() {}) seguem as regras da variável usada (neste caso, const), ou seja, o hoisting ocorre mas a variável não é inicializada, e a função só estará disponível após a atribuição.
// Exemplo com var
console.log(a); // undefined
var a = 10;
console.log(a); // 10
// Exemplo com let
console.log(b); // ReferenceError: Cannot access 'b' before initialization
let b = 20;
// Exemplo com const
console.log(c); // ReferenceError: Cannot access 'c' before initialization
const c = 30;
// Exemplo com função declarada
foo(); // "Função foo executada"
function foo() {
console.log("Função foo executada");
}
// Exemplo com expressão de função
bar(); // TypeError: bar is not a function (ou ReferenceError dependendo do hoisting)
const bar = function() {
console.log("Expressão de função");
};
Temporal Dead Zone
A Temporal Dead Zone (TDZ) é o período entre a entrada no escopo (por exemplo, o início de um bloco) e a declaração real da variável com let ou const. Durante a TDZ, a variável existe no escopo, mas não pode ser acessada, resultando em um ReferenceError. A TDZ existe para evitar que variáveis sejam usadas antes de sua inicialização, promovendo um código mais seguro.
Um exemplo clássico de TDZ ocorre em blocos if ou loops for. No código abaixo, a variável x está na TDZ até a linha da declaração:
{
// Início da TDZ para x
console.log(x); // ReferenceError
let x = 5;
// Fim da TDZ, x pode ser acessado
console.log(x); // 5
}
É importante notar que a TDZ também afeta variáveis const da mesma forma, com a diferença adicional de que const exige inicialização na declaração e não pode ser reatribuída.
Exemplos
Vamos analisar alguns exemplos práticos que ilustram o hoisting e a TDZ em ação.
Exemplo 1: Hoisting de var em loops
for (var i = 0; i < 3; i++) {
setTimeout(() => console.log(i), 100);
}
// Saída: 3, 3, 3 (devido ao hoisting de var, i tem escopo de função)
Como var tem escopo de função (ou global), a variável i é içada para o escopo da função (ou global) e é compartilhada entre todas as iterações. Quando os timeouts executam, o loop já terminou e i vale 3. Para corrigir, use let:
for (let i = 0; i < 3; i++) {
setTimeout(() => console.log(i), 100);
}
// Saída: 0, 1, 2 (cada iteração tem seu próprio escopo de bloco)
Exemplo 2: TDZ com typeof
Usar typeof em uma variável na TDZ também lança um erro, ao contrário de variáveis não declaradas que retornam "undefined":
console.log(typeof undeclaredVar); // "undefined" (variável não declarada)
console.log(typeof letVar); // ReferenceError: Cannot access 'letVar' before initialization
let letVar = 10;
Exemplo 3: Hoisting de funções e expressões
// Função declarada: hoisting completo
sayHello(); // "Hello!"
function sayHello() {
console.log("Hello!");
}
// Expressão de função: hoisting da variável (const) mas não da função
sayGoodbye(); // ReferenceError: Cannot access 'sayGoodbye' before initialization
const sayGoodbye = function() {
console.log("Goodbye!");
};
Boas práticas
Para evitar surpresas com hoisting, declare todas as variáveis no topo de seus escopos (função ou bloco). Prefira const por padrão e let quando precisar reatribuir. Evite var sempre que possível, pois seu comportamento de hoisting pode causar confusão. Além disso, evite depender do hoisting de funções para chamá-las antes da declaração; embora funcione, pode tornar o código menos legível. Mantenha as declarações de funções no início do escopo.
Referências
- MDN: Hoisting
- MDN: var
- MDN: let
- MDN: const
- MDN: function
- MDN: Expressão de função
- ECMAScript Language Specification
Exercícios
- Qual será a saída do código abaixo? Explique o comportamento.
console.log(a); var a = 5; console.log(a);✓ Resposta: A saída seráundefinede depois5. A declaraçãovar aé içada para o topo do escopo, e a variável é inicializada comundefined. O primeiroconsole.logexibeundefined. Em seguida, a atribuiçãoa = 5ocorre, e o segundoconsole.logexibe5. - O que acontece se tentarmos acessar uma variável
letantes de sua declaração? Dê um exemplo.✓ Resposta: A tentativa resulta em umReferenceError, pois a variável está na Temporal Dead Zone. Exemplo:console.log(x); // ReferenceError: Cannot access 'x' before initialization let x = 10; - Qual a diferença de hoisting entre uma função declarada e uma expressão de função atribuída a uma variável
const?✓ Resposta: A função declarada é completamente içada, podendo ser chamada antes da declaração. Já a expressão de função atribuída a uma variávelconstsegue o hoisting deconst: a variável é içada mas não inicializada, ficando na TDZ. Tentar chamá-la antes da atribuição resulta emReferenceError. - O código a seguir gera erro? Se sim, qual e por quê?
{ console.log(foo); const foo = "bar"; }✓ Resposta: Sim, gera umReferenceError: Cannot access 'foo' before initialization. A variávelfoodeclarada comconstestá na TDZ desde o início do bloco até a linha da declaração. Oconsole.logtenta acessá-la antes da inicialização, causando o erro. - Explique o que é a Temporal Dead Zone e por que ela existe.✓ Resposta: A Temporal Dead Zone (TDZ) é o período entre a entrada no escopo (por exemplo, o início de um bloco) e a declaração de uma variável com
letouconst. Durante a TDZ, a variável não pode ser acessada, e qualquer tentativa resulta emReferenceError. A TDZ existe para evitar o uso de variáveis antes de sua inicialização, tornando o código mais previsível e seguro, ao contrário do comportamento devarque permite acesso aundefined.