Hoisting e Scope: como o JavaScript decide onde as coisas existem
11 de abril de 2026 às 10:18 - Por Larissa Santos

Se você já tomou um undefined inesperado, ou um ReferenceError em um lugar que parecia impossível, hoisting ou scope estavam envolvidos. Esses dois conceitos definem como o JavaScript enxerga o seu código antes e durante a execução.
Por que isso importa
O JavaScript não lê o código de cima para baixo da forma que parece. Antes de executar qualquer linha, o engine passa por uma fase de compilação onde registra todas as declarações. É nessa fase que o hoisting acontece.
Entender isso muda como você interpreta erros, como você organiza funções e por que var se comporta de forma diferente de let e const em contextos que parecem idênticos.
O que é hoisting
Hoisting é o comportamento do engine de registrar declarações antes da execução começar. Popularmente se diz que as declarações "sobem para o topo do código", mas o que acontece de verdade é que elas são processadas na fase de compilação, antes de qualquer atribuição de valor.
O que sobe e o que fica no lugar depende do tipo da declaração.
Com var, a declaração sobe, mas o valor não:
console.log(nome) // undefined
var nome = 'Ana'
console.log(nome) // "Ana"
O que o engine processa é algo como:
var nome // içado, valor = undefined
console.log(nome) // undefined
nome = 'Ana'
console.log(nome) // "Ana"
A linha var nome vai ao topo. A atribuição = "Ana" permanece exatamente onde foi escrita.
Com let e const, o comportamento muda. As declarações também são içadas, mas ficam em uma zona morta temporal, a Temporal Dead Zone, até que a linha de declaração seja atingida em execução. Qualquer acesso antes disso gera um ReferenceError:
console.log(idade) // ReferenceError
let idade = 25
Isso não é um bug. É intencional. O comportamento foi projetado exatamente para evitar leitura de variáveis em estado indefinido, um problema silencioso que var carrega consigo.
Com function declarations, o hoisting é completo. Declaração e corpo sobem juntos:
saudar() // "Olá!"
function saudar() {
console.log('Olá!')
}
Com function expressions e arrow functions, a função é apenas um valor atribuído a uma variável. O hoisting se aplica à variável, não à função:
cumprimentar() // TypeError: cumprimentar is not a function
var cumprimentar = function () {
console.log('Oi!')
}
var cumprimentar é içado como undefined. Chamar undefined como função gera um TypeError. Se fosse const ou let, seria um ReferenceError pela TDZ.
O que é scope
Scope define onde uma variável pode ser acessada. O JavaScript trabalha com três níveis.
Scope global é o nível mais externo. Variáveis declaradas aqui estão disponíveis em qualquer parte do código. Em aplicações maiores, isso vira um problema rápido: qualquer coisa pode ler e sobrescrever essas variáveis.
Function scope isola variáveis dentro de funções. Uma variável declarada com var dentro de uma função não existe fora dela:
function calcular() {
var resultado = 42
}
console.log(resultado) // ReferenceError
Block scope é o comportamento de let e const. Qualquer bloco delimitado por {} cria um escopo próprio:
if (true) {
let mensagem = 'dentro do bloco'
console.log(mensagem) // "dentro do bloco"
}
console.log(mensagem) // ReferenceError
var não respeita block scope. Ele respeita apenas o escopo da função mais próxima:
if (true) {
var valor = 10
}
console.log(valor) // 10
Esse é um dos motivos pelos quais var costuma ser fonte de bugs difíceis de rastrear. Você declara dentro de um if, de um for, de qualquer bloco, e a variável vaza para o escopo da função inteira.
Como os dois se conectam
Hoisting e scope não operam de forma isolada. O hoisting sempre acontece dentro de um escopo. Uma variável é içada para o topo do escopo em que foi declarada, não para o topo do arquivo.
function exemplo() {
console.log(x) // undefined, não ReferenceError
var x = 5
}
console.log(x) // ReferenceError
Dentro da função, x é içado para o topo do function scope. Fora da função, ela simplesmente não existe.
Isso explica um comportamento que confunde bastante em loops com var:
for (var i = 0; i < 3; i++) {
setTimeout(() => console.log(i), 100)
}
// 3, 3, 3
var i não fica presa no bloco do for. Ela é içada para o escopo da função (ou global) e existe como uma única variável. Quando os callbacks executam, o loop já terminou e i é 3.
Com let, cada iteração cria um escopo próprio:
for (let i = 0; i < 3; i++) {
setTimeout(() => console.log(i), 100)
}
// 0, 1, 2
Conclusão
Hoisting e scope são a base de como o JavaScript organiza o seu código antes de executá-lo. Quando você entende que o engine processa declarações antes de valores, e que cada tipo de declaração tem um comportamento diferente nesse processo, os erros que antes pareciam aleatórios começam a fazer sentido.
var em loop vaza para fora do bloco porque var não respeita block scope. undefined antes da declaração acontece porque a declaração subiu mas o valor não. ReferenceError em let acontece porque a TDZ protege você de ler uma variável que ainda não foi inicializada.
O JavaScript não está sendo imprevisível. Ele está seguindo regras bem definidas de onde as coisas vivem e quando ficam disponíveis.