var, let, const e Escopo: O Que Realmente Muda
Se você já olhou JavaScript escrito antes de mais ou menos 2015 e JavaScript escrito hoje, uma diferença salta aos olhos na hora: var estava em todo lugar, e hoje você vê principalmente let e const. Entender por quê é uma das formas mais rápidas de parar de se confundir com bugs que parecem não fazer sentido.
O que "escopo" significa
Escopo é só a resposta para uma pergunta: "De onde no meu código eu consigo acessar essa variável?"
function cumprimentar() {
const mensagem = "Olá!"
console.log(mensagem) // funciona — estamos dentro da mesma função
}
console.log(mensagem) // ❌ ReferenceError: mensagem não está definida
mensagem só existe dentro de cumprimentar. Fora dela, é como se essa variável nunca tivesse existido. Isso é escopo: uma variável só é visível dentro do "bloco" (basicamente, a região entre { }) onde foi criada — e em qualquer bloco aninhado dentro desse.
A diferença central: escopo de função vs escopo de bloco
Essa é a diferença real entre var e let/const — todo o resto decorre disso.
vartem escopo de função. Ele ignora completamente blocos{ }comoifefor— só é contido pela função mais próxima (ou pelo escopo global, se não houver função).leteconsttêm escopo de bloco. Eles respeitam o{ }— uma variável declarada dentro de um blocoifnão vaza para fora dele.
Aqui está o bug que isso causa na prática:
if (true) {
var x = 10
}
console.log(x) // 10 — o var vazou para fora do bloco if!
if (true) {
let y = 10
}
console.log(y) // ❌ ReferenceError — o let ficou dentro do bloco, como esperado
Esse "vazamento" é exatamente por que o var causa bugs confusos em projetos maiores: uma variável que você pensava ser temporária e local, na verdade, ainda está viva em algum lugar onde não deveria estar.
O bug clássico do loop
Este é o exemplo mais usado para explicar por que o var causa problemas reais:
for (var i = 0; i < 3; i++) {
setTimeout(() => console.log(i), 0)
}
// Imprime: 3, 3, 3 (e não 0, 1, 2, como você esperaria!)
Isso acontece porque o var não cria um novo i a cada repetição do loop — existe apenas um i, compartilhado por todo o loop, e quando os callbacks do setTimeout finalmente rodam, o loop já terminou e i vale 3.
for (let i = 0; i < 3; i++) {
setTimeout(() => console.log(i), 0)
}
// Imprime: 0, 1, 2 — como esperado
O let cria um i novo a cada repetição, então cada callback captura seu próprio valor separado. Esse único comportamento é boa parte do motivo pelo qual o let substituiu o var como padrão.
let vs const
Uma vez com escopo de bloco resolvido, a escolha entre let e const é simples: é sobre se você pretende reatribuir a variável, não sobre se o conteúdo dela pode mudar.
const nome = "Ana"
nome = "Beatriz" // ❌ TypeError — const não pode ser reatribuída
const usuario = { nome: "Ana" }
usuario.nome = "Beatriz" // ✅ totalmente válido — não estamos reatribuindo `usuario`,
// estamos alterando o objeto para o qual ele aponta
const não significa "congelado para sempre" — significa "essa variável sempre vai apontar para o mesmo valor". Se esse valor for um objeto ou array, o conteúdo dele ainda pode mudar; você só não pode fazer usuario apontar para um objeto completamente diferente.
Uma regra prática
- Use
constpor padrão, para tudo. - Mude para
letsó quando souber que a variável precisa ser reatribuída (um contador de loop, um valor que muda com base numa condição). - Evite
var— não existe situação no JavaScript moderno onde ele faz algo queletouconstnão façam melhor e de forma mais previsível.
Isso não é preferência de estilo — seguir essa regra elimina uma categoria inteira de bugs (o bug do loop acima, e suas variações) antes mesmo de você precisar depurá-los.