Pular para o conteúdo
Cursos

DevClub

LógicaFront-endBack-endMobile

IA Club

IA na prática
Estudar programaçãoEstudar IA
LiçãoIniciantecódigo testado

Variáveis em JavaScript: var, let e const na prática

Escopo de bloco contra escopo de função, zona morta temporal e por que const com objeto continua mutável — com o erro real de cada caso no Node.

Rodolfo Mori5 min de leitura

Variável é um nome dado a um valor guardado na memória. Em JavaScript existem três formas de criar uma, e a escolha entre elas muda o comportamento do programa — não só o estilo.

A regra curta, para você já sair usando: const por padrão, let quando o valor precisa mudar, var nunca em código novo. O resto deste texto é o porquê, com o erro de cada caso rodando no terminal.

Três crachás, três regras de acesso

Pense em três crachás para a mesma sala. const deixa você trabalhar com o que está lá, mas não trocar a sala apontada pelo crachá. let permite apontar para outro valor quando o programa precisa. var usa uma regra antiga de acesso: ignora limites de bloco e pode aparecer antes da declaração como undefined.

Os nomes técnicos são declaração, reatribuição e escopo. A analogia do crachá não quer dizer que const congele objetos; ela trava a ligação do nome com aquele objeto, não o que existe dentro dele. Antes de entrar nos detalhes, faça três tentativas: reatribua um const, mude uma propriedade de objeto guardado em const e leia um let fora do bloco. Os três resultados diferentes são o mapa desta lição.

O básico

js
const NOME_DA_LOJA = 'Club Store';
let itensNoCarrinho = 0;

itensNoCarrinho = itensNoCarrinho + 1;
itensNoCarrinho += 2;

console.log(NOME_DA_LOJA, itensNoCarrinho);
Club Store 3

const cria um nome que não pode ser reapontado. Tentar reatribuir é erro em tempo de execução:

js
const CUPOM = 'PRIMEIRACOMPRA';

CUPOM = 'BLACKFRIDAY';
file:///private/tmp/loja/cupom.mjs:3 CUPOM = 'BLACKFRIDAY'; ^ TypeError: Assignment to constant variable. at file:///private/tmp/loja/cupom.mjs:3:7 Node.js v24.16.0

Escopo de bloco contra escopo de função

Bloco é qualquer coisa entre chaves: o corpo de um if, de um for, de uma função. let e const só existem dentro do bloco onde foram declarados.

js
const total = 250;

if (total > 199) {
  let frete = 0;
  console.log('frete dentro do if:', frete);
}

console.log('frete fora do if:', frete);
frete dentro do if: 0 file:///private/tmp/loja/v03.mjs:8 console.log('frete fora do if:', frete); ^ ReferenceError: frete is not defined at file:///private/tmp/loja/v03.mjs:8:34 Node.js v24.16.0

Com var, a variável ignora o bloco e sobe para o escopo da função inteira (ou do módulo, se estiver no topo):

js
const total = 250;

if (total > 199) {
  var frete = 0;
}

console.log('frete fora do if:', frete);
frete fora do if: 0

Parece conveniente. Não é: significa que uma variável criada dentro de um if continua viva — e escrevível — pelo resto da função, atropelando qualquer outra com o mesmo nome.

O caso que mais confunde: var dentro de laço

js
const precos = [19.9, 49.9, 129.9];

for (var i = 0; i < precos.length; i++) {
  setTimeout(() => console.log('var  ->', i, precos[i]), 0);
}

for (let j = 0; j < precos.length; j++) {
  setTimeout(() => console.log('let  ->', j, precos[j]), 0);
}
var -> 3 undefined var -> 3 undefined var -> 3 undefined let -> 0 19.9 let -> 1 49.9 let -> 2 129.9

Com var existe uma variável i, compartilhada pelas três funções. Quando o setTimeout finalmente roda, o laço já terminou e i vale 3 — daí o undefined, porque precos[3] não existe.

Com let, cada volta do laço ganha o seu próprio j. Cada função guardou um valor diferente. Se este mecanismo ainda parecer mágico, ele fica claro depois de funções em JavaScript: o que cada callback guarda é o escopo em que nasceu.

Zona morta temporal (TDZ)

let e const são içados para o topo do bloco, mas ficam inacessíveis até a linha da declaração. Esse intervalo é a zona morta temporal.

js
console.log('estoque atual:', estoque);

let estoque = 12;
file:///private/tmp/loja/estoque.mjs:1 console.log('estoque atual:', estoque); ^ ReferenceError: Cannot access 'estoque' before initialization at file:///private/tmp/loja/estoque.mjs:1:31 Node.js v24.16.0

Repare que a mensagem não é estoque is not defined. O motor sabe que estoque existe naquele bloco; ele está te dizendo que você chegou cedo demais.

Com var, nada disso acontece — e é aí que mora o problema:

js
console.log('estoque atual:', estoque);

var estoque = 12;

console.log('estoque depois:', estoque);
estoque atual: undefined estoque depois: 12

O programa não quebrou; ele imprimiu undefined e seguiu adiante. Uma hora esse undefined vira uma conta errada três funções depois, e você vai caçar a causa no lugar errado. A TDZ do let é uma funcionalidade, não um incômodo: ela falha no lugar onde o erro foi cometido.

Redeclarar

let e const não deixam você declarar o mesmo nome duas vezes no mesmo escopo. E este erro é de sintaxe: acontece antes de qualquer linha rodar.

js
let quantidade = 1;
let quantidade = 2;

console.log(quantidade);
file:///private/tmp/loja/v10.mjs:2 let quantidade = 2; ^ SyntaxError: Identifier 'quantidade' has already been declared Node.js v24.16.0

Nem o console.log da linha 4 chegou a existir. var aceita a redeclaração em silêncio, o que é exatamente como dois trechos de código passam a brigar pela mesma variável sem ninguém perceber.

const com objeto continua mutável

Esta é a confusão mais frequente da lição. const protege o vínculo entre o nome e o valor, não o conteúdo do valor.

js
const carrinho = {
  cliente: 'Ana',
  itens: ['Teclado mecânico'],
};

carrinho.itens.push('Mouse sem fio');
carrinho.cliente = 'Ana Souza';

console.log(carrinho);
{ cliente: 'Ana Souza', itens: [ 'Teclado mecânico', 'Mouse sem fio' ] }

Nenhum erro. O que const impediria é carrinho = { ... } — apontar o nome para outro objeto. Mexer por dentro é permitido, e é justamente por isso que a maior parte do código real usa const para arrays e objetos.

Se você quiser mesmo travar o conteúdo, existe Object.freeze:

js
const config = Object.freeze({
  moeda: 'BRL',
  freteGratisAcimaDe: 199,
});

config.freteGratisAcimaDe = 99;

console.log(config.freteGratisAcimaDe);
file:///private/tmp/loja/v09.mjs:6 config.freteGratisAcimaDe = 99; ^ TypeError: Cannot assign to read only property 'freteGratisAcimaDe' of object '#<Object>' at file:///private/tmp/loja/v09.mjs:6:27 Node.js v24.16.0

Object.freeze também é raso: ele congela o primeiro nível, não os objetos aninhados dentro dele.

Nomes

O motor aceita quase qualquer nome; quem lê o código, não. Três hábitos que poupam tempo:

  • Nome diz o que é, não o tipo: precos, não arrayDePrecos.
  • Booleano começa com verbo de estado: temFreteGratis, estaLogado.
  • Constante de configuração, definida uma vez e nunca mais tocada, em MAIUSCULA_COM_UNDERSCORE. Uma const comum dentro de função não precisa disso.
js
const precos = [19.9, 49.9, 129.9];
const total = precos.reduce((soma, preco) => soma + preco, 0);

const freteGratis = total >= 199;
console.log(total.toFixed(2), freteGratis);
199.70 true

Resumo

var let const
escopo função bloco bloco
içada e acessível antes sim, como undefined não (TDZ) não (TDZ)
reatribuir sim sim não
redeclarar no mesmo escopo sim não não

A próxima lição da trilha de JavaScript é sobre funções — onde escopo deixa de ser teoria e passa a decidir o que cada pedaço do seu código consegue enxergar. O guia completo de JavaScript mostra a trilha inteira em ordem.

Prefere aprender em vídeo?

Tem uma aula sobre este assunto no nosso canal.

Ver todos os vídeos do canal
  • variaveis
  • let
  • const
  • var
  • escopo

Perguntas frequentes

Ainda preciso aprender var?
Para escrever código novo, não. Para ler código antigo e entender bug de escopo em projeto legado, sim. Saber o que var faz de diferente é o que permite explicar por que um laço antigo imprime sempre o último valor.
Qual a diferença entre não definida e undefined?
undefined é um valor: a variável existe e ainda não recebeu nada. ReferenceError não definida quer dizer que o nome não existe em nenhum escopo visível dali. Um é estado, o outro é ausência.
Se const não impede alterar o objeto, para que serve?
Const congela a ligação entre o nome e o valor, não o conteúdo do valor. Isso já elimina uma classe inteira de bug: ninguém troca seu array por outro no meio do arquivo. Para congelar o conteúdo, use Object.freeze.

Dúvidas e comentários

Travou em algum passo? Pergunte aqui — a equipe e outros alunos respondem.

Todo o código deste artigo foi executado em Node 24.16.0, e as saídas exibidas são as reais — como produzimos este conteúdo.

Fontes consultadas

  1. MDN — Grammar and types: Declarations — developer.mozilla.org
  2. ECMAScript 2026 Language Specification — Let and Const Declarations — tc39.es

Continue por aqui