Hoisting em profundidade
Esta aula explora em profundidade o mecanismo de hoisting em JavaScript, detalhando como variáveis declaradas com var, let, const e funções são içadas, e introduz o conceito de Temporal Dead Zone. Através de exemplos práticos, o aluno compreenderá as diferenças de comportamento e boas práticas para evitar erros comuns.
Hoisting é um comportamento do JavaScript onde declarações de variáveis e funções são movidas para o topo de seu escopo 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 com comportamentos diferentes dependendo da palavra-chave utilizada. Entender profundamente o hoisting é essencial para evitar bugs sutis e escrever código previsível.
Nesta aula, vamos desmistificar o hoisting para declarações com var, let, const e funções, explorar a Temporal Dead Zone (TDZ) e analisar exemplos práticos que ilustram cada conceito.
Hoisting de var, let, const e funções
O hoisting ocorre de forma diferente para cada tipo de declaração. Com var, a declaração é içada para o topo do escopo da função ou global, mas a inicialização permanece no local original. Isso significa que a variável existe (com valor undefined) desde o início do escopo. Já com let e const, a declaração também é içada, mas a variável não é inicializada até a linha da declaração ser executada, ficando em uma zona morta temporal (TDZ) entre o início do escopo e a declaração.
Para funções declaradas com a sintaxe function nome() {}, tanto a declaração quanto a definição são içadas, permitindo chamar a função antes de sua declaração. Já expressões de função (como const fn = function() {}) seguem as regras da variável usada (var, let, const), e apenas a declaração da variável é içada (se var) ou fica na TDZ (se let/const).
// Exemplo com var
console.log(a); // undefined (hoisting)
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
const c = 30;
// Exemplo com função
foo(); // "foo"
function foo() {
console.log("foo");
}
// Expressão de função com var
bar(); // TypeError: bar is not a function (pois bar é undefined)
var bar = function() {
console.log("bar");
};
// Expressão de função com let
baz(); // ReferenceError
let baz = function() {
console.log("baz");
};Temporal Dead Zone
A Temporal Dead Zone (TDZ) é o período entre a entrada no escopo (bloco) e a declaração real da variável com let ou const. Durante a TDZ, qualquer tentativa de acessar a variável resulta em um ReferenceError. Esse comportamento garante que a variável não seja usada antes de sua inicialização, evitando bugs comuns associados ao hoisting de var.
A TDZ também se aplica a parâmetros de funções com valores padrão e a classes. Por exemplo, se você tentar acessar uma variável let dentro de um bloco antes de sua declaração, mesmo que a variável esteja no escopo, ela está na TDZ. O mesmo vale para const. É importante notar que a TDZ é estritamente temporal, não espacial: ela depende da ordem de execução, não da posição no código.
// Exemplo de TDZ
{
// TDZ para x começa aqui
console.log(x); // ReferenceError
let x = 5;
// TDZ termina aqui
}
// TDZ com typeof
{
console.log(typeof x); // ReferenceError (diferente de var, que retorna undefined)
let x = 10;
}
// TDZ em parâmetros padrão
function test(y = x) {
let x = 1;
console.log(y); // ReferenceError: Cannot access 'x' before initialization
}
test();
// TDZ em classes
const p = new Polygon(); // ReferenceError
class Polygon {}Exemplos
Vamos consolidar os conceitos com exemplos mais elaborados que mostram as interações entre hoisting, TDZ e escopos. O primeiro exemplo ilustra como o hoisting de var pode causar comportamentos inesperados em loops, enquanto o segundo mostra a segurança de let em blocos.
// Exemplo 1: var em loop
for (var i = 0; i < 3; i++) {
setTimeout(() => console.log(i), 100); // 3, 3, 3 (devido ao hoisting de var)
}
console.log(i); // 3 (i vazou do loop)
// Exemplo 2: let em loop
for (let j = 0; j < 3; j++) {
setTimeout(() => console.log(j), 100); // 0, 1, 2 (cada iteração tem seu próprio j)
}
console.log(j); // ReferenceError: j is not defined
// Exemplo 3: Hoisting de função vs var
function test() {
console.log(foo); // undefined (hoisting de var)
console.log(bar); // [Function: bar] (hoisting completo)
var foo = "foo";
function bar() {
return "bar";
}
}
test();
// Exemplo 4: TDZ com bloco aninhado
let x = "global";
{
console.log(x); // ReferenceError: Cannot access 'x' before initialization
let x = "local";
}Boas Práticas
Para evitar problemas com hoisting, recomenda-se declarar todas as variáveis e funções no topo do escopo (função ou bloco) de forma explícita. Utilize let e const em vez de var para ter um comportamento mais previsível e evitar vazamentos de escopo. Sempre declare variáveis antes de usá-las, e evite depender do hoisting de funções para chamadas antes da declaração, pois isso pode tornar o código menos legível.
Referências
- MDN: Hoisting
- MDN: var
- MDN: let
- MDN: const
- MDN: function
- MDN: Expressões de função
- ECMAScript: Temporal Dead Zone
Exercícios
O que será exibido no console do código abaixo? Explique o comportamento.
console.log(a); var a = 1; console.log(a);✓ Resposta: Será exibidoundefinede depois1. Isso ocorre porque a declaraçãovar aé içada para o topo do escopo, mas a atribuição= 1permanece no lugar. Portanto, no primeiroconsole.log,aexiste mas valeundefined; após a atribuição, vale1.O que acontece ao executar o código a seguir? Justifique.
{ console.log(x); let x = 2; }✓ Resposta: Ocorre umReferenceError: Cannot access 'x' before initialization. A variávelxcomleté içada, mas entra na Temporal Dead Zone desde o início do bloco até a linha da declaração. Tentar acessá-la antes da declaração gera erro.Analise o código e diga qual será a saída:
foo(); function foo() { console.log("foo"); } bar(); var bar = function() { console.log("bar"); };✓ Resposta: A saída será"foo"seguido deTypeError: bar is not a function. A funçãofooé içada completamente, então pode ser chamada antes da declaração. Jábaré uma expressão de função atribuída a uma variávelvar; apenas a declaraçãovar baré içada (inicializada comoundefined), então ao tentar chamarbar(), ela não é uma função.Explique o que é a Temporal Dead Zone e dê um exemplo que a demonstre.
✓ Resposta: A Temporal Dead Zone (TDZ) é o período entre o início do escopo (bloco) e a declaração de uma variável comletouconst, durante o qual a variável existe mas não pode ser acessada, resultando emReferenceError. Exemplo:{ // TDZ para x começa console.log(x); // ReferenceError let x = 10; // TDZ termina }Qual a diferença de hoisting entre uma declaração de função (
function nome() {}) e uma expressão de função atribuída a uma variávellet?✓ Resposta: Na declaração de função, tanto a declaração quanto o corpo da função são içados, permitindo chamá-la antes da linha de definição. Já na expressão de função comlet, apenas a declaração da variável é içada (mas fica na TDZ até a linha de declaração), e a atribuição da função ocorre no local. Portanto, tentar chamá-la antes da declaração resulta emReferenceError(devido à TDZ), e não emTypeErrorcomo comvar.