Closures em JavaScript: a função que lembra
Entenda o que é uma closure, como uma função guarda acesso às variáveis de onde nasceu, onde isso aparece no dia a dia (contadores, hooks, módulos) e as armadilhas clássicas com var e loops.
Closure é um daqueles termos que aparecem em toda entrevista e que muita gente sabe usar sem saber nomear. Você já escreveu closures hoje, provavelmente, a cada callback e a cada hook. Entender o conceito explica comportamentos que de outro jeito parecem mágica ou bug.
A ideia em uma frase
Uma closure é uma função que continua tendo acesso às variáveis do lugar onde foi criada, mesmo depois que esse lugar já terminou de executar.
function criarContador() {
let total = 0;
return function () {
total++;
return total;
};
}
const contar = criarContador();
contar(); // 1
contar(); // 2
contar(); // 3Quando criarContador termina, o normal seria total desaparecer. Mas a função devolvida ainda usa essa variável, então o JavaScript a mantém viva. A função "lembra" do ambiente onde nasceu.
Cada closure tem a sua cópia
Cada chamada a criarContador cria um ambiente novo, com o seu próprio total:
const a = criarContador();
const b = criarContador();
a(); // 1
a(); // 2
b(); // 1, o contador de b é independenteIsso é o que permite criar várias instâncias com estado próprio sem usar classe.
Dados privados
Como total só é acessível por dentro, ninguém de fora consegue mexer nele diretamente. Closures são a forma clássica de ter variáveis privadas em JavaScript:
function criarConta(saldoInicial) {
let saldo = saldoInicial;
return {
depositar(valor) {
saldo += valor;
},
consultar() {
return saldo;
},
};
}
const conta = criarConta(100);
conta.depositar(50);
conta.consultar(); // 150
conta.saldo; // undefined, não existe acesso diretoOnde você já usa closures
Callbacks e eventos. A função passada pro setTimeout ou pro addEventListener guarda as variáveis do escopo em volta:
function agendarAviso(nome) {
setTimeout(() => {
console.log(`Olá, ${nome}`); // "nome" vem da closure
}, 1000);
}Funções que geram funções. Quando você quer configurar um comportamento uma vez e reutilizar:
const multiplicarPor = (fator) => (numero) => numero * fator;
const dobrar = multiplicarPor(2);
dobrar(5); // 10Hooks do React. Um componente React é uma função que roda a cada renderização, e cada função interna (um handler de clique, um useEffect) captura os valores daquela renderização específica:
function Contagem() {
const [n, setN] = useState(0);
function mostrarDepois() {
setTimeout(() => alert(n), 3000); // mostra o valor de n de quando o clique aconteceu
}
return <button onClick={mostrarDepois}>Ver</button>;
}Se você clicar, depois incrementar n antes dos 3 segundos, o alerta mostra o valor antigo. Não é bug, é a closure guardando o n daquela renderização. Esse mesmo mecanismo explica o famoso problema de "estado velho" dentro de useEffect com array de dependências incompleto.
A armadilha clássica: var em loop
for (var i = 0; i < 3; i++) {
setTimeout(() => console.log(i), 100);
}
// imprime 3, 3, 3Todas as funções compartilham a mesma variável i, porque var tem escopo de função, não de bloco. Quando os timeouts disparam, o loop já acabou e i vale 3. A correção é usar let, que cria um i novo a cada volta:
for (let i = 0; i < 3; i++) {
setTimeout(() => console.log(i), 100);
}
// imprime 0, 1, 2O custo: memória
Como a closure mantém as variáveis vivas, elas não são coletadas pelo garbage collector enquanto a função existir. Na maioria dos casos isso é irrelevante, mas uma closure que guarda um objeto enorme e fica registrada num evento que nunca é removido causa vazamento de memória. Se você registra um listener, lembre de removê-lo quando não for mais necessário.
Fechando
Closure é a função carregando o ambiente onde foi criada. Ela explica contadores com estado privado, funções fábrica, o comportamento dos hooks e o bug do var em loops. Quando um valor aparecer "velho" ou "errado" dentro de um callback, a primeira pergunta é: de qual escopo essa função está lembrando?
Direto na sua
caixa de entrada.
Um aviso por e-mail sempre que eu publicar um post novo. Sem spam, sem newsletter chata, só isso.
Leia também
- Paginação: offset vs cursor em listas grandes9 de novembro de 2026 · 4 min
- Debounce e throttle: eventos sem travar a tela8 de novembro de 2026 · 4 min
- Jev com Hermes Agent: 4 usos para automatizar tarefas7 de novembro de 2026 · 6 min
- Regex sem medo: o básico que resolve 90% dos casos6 de novembro de 2026 · 4 min