Closures em JavaScript: o conceito que parece difícil mas não é
← Voltar para Codeshort

Closures em JavaScript: o conceito que parece difícil mas não é

Closure não é magia. É só uma função que lembra do escopo onde foi criada — e entender isso muda como você escreve JavaScript.

DC
Dev Code Software
03 de junho de 2026·8 min de leitura

O que é uma closure, sem rodeios

Toda vez que você cria uma função dentro de outra função, a função interna carrega consigo o escopo da externa. Esse "pacote" — função + variáveis do escopo onde ela foi criada — é o que chamamos de closure.

Simples assim.

function criarContador() {
  let count = 0;

  return function () {
    count++;
    return count;
  };
}

const contador = criarContador();
console.log(contador()); // 1
console.log(contador()); // 2
console.log(contador()); // 3

A variável count não existe mais no escopo global. Mas a função retornada ainda tem acesso a ela — porque foi criada dentro do escopo onde count vivia. Isso é uma closure.

Por que funções "lembram" de variáveis

Quando o JavaScript executa criarContador(), cria um novo contexto de execução com count = 0. Quando criarContador termina, esse contexto normalmente seria descartado pelo garbage collector.

Mas não nesse caso. Por quê?

Porque a função interna — aquela que foi retornada — ainda mantém uma referência ao ambiente onde foi criada. O garbage collector é inteligente o suficiente para perceber isso e não liberar a memória enquanto a referência existir.

function criarSaudacao(prefixo) {
  // `prefixo` fica "vivo" enquanto a função retornada existir
  return function (nome) {
    return `${prefixo}, ${nome}!`;
  };
}

const ola = criarSaudacao("Olá");
const hey = criarSaudacao("Hey");

console.log(ola("Rafael")); // "Olá, Rafael!"
console.log(hey("Ana"));   // "Hey, Ana!"

Cada chamada a criarSaudacao cria uma closure diferente, com seu próprio prefixo. As duas coexistem em memória sem interferência.

💡 Dica: Closures não compartilham estado entre si. Cada instância da função externa gera um ambiente independente.

Closures no código do dia a dia

Você já usa closures o tempo todo, mesmo sem perceber. Callbacks, handlers de evento, funções de configuração — tudo isso costuma depender de closures.

// Handler de botão com closure
function configurarBotao(elemento, mensagem) {
  elemento.addEventListener("click", function () {
    // `mensagem` vem do escopo externo — closure
    alert(mensagem);
  });
}

const btn = document.querySelector("#meu-btn");
configurarBotao(btn, "Formulário enviado com sucesso!");

No React, hooks como useState e useCallback também são closure-driven. O próprio useEffect com dependências é um exemplo direto: quando você captura um valor do escopo do componente dentro do efeito, está formando uma closure.

// React — closure com estado
function Contador() {
  const [count, setCount] = useState(0);

  const handleClick = useCallback(() => {
    // `count` capturado via closure
    console.log(`Valor atual: ${count}`);
    setCount(count + 1);
  }, [count]);

  return <button onClick={handleClick}>{count}</button>;
}

Essa é, aliás, a raiz do famoso bug de "stale closure" no React — quando a closure captura um valor antigo e o componente não re-renderiza como esperado. Mas isso é papo para outro post.

O erro clássico com closures em loops

Esse aqui apareceu num PR meu anos atrás. O revisor não disse nada — o bug só apareceu em teste manual, porque todos os botões faziam a mesma coisa.

// ❌ Todos os handlers imprimem 3
for (var i = 0; i < 3; i++) {
  document.querySelectorAll(".btn")[i].addEventListener("click", function () {
    console.log(i); // sempre 3
  });
}

O problema: var tem escopo de função, não de bloco. Quando o loop termina, i vale 3. Todas as closures criadas dentro do loop apontam para a mesma variável i.

// ✓ Cada handler tem seu próprio `i`
for (let i = 0; i < 3; i++) {
  document.querySelectorAll(".btn")[i].addEventListener("click", function () {
    console.log(i); // 0, 1 ou 2 — correto
  });
}

let tem escopo de bloco. A cada iteração, o JavaScript cria um novo binding de i, e cada closure captura o seu próprio.

Antes do ES6, o padrão era criar uma IIFE (Immediately Invoked Function Expression) para forçar esse isolamento:

// Solução pré-ES6 com IIFE
for (var i = 0; i < 3; i++) {
  (function (indice) {
    document.querySelectorAll(".btn")[indice].addEventListener("click", function () {
      console.log(indice);
    });
  })(i);
}

Funciona pelo mesmo princípio: cada chamada da IIFE cria um escopo novo, com seu próprio indice.

⚠️ Atenção: Se você ainda usa var em código novo, esse bug vai aparecer em algum momento. Prefira sempre let e const.

Casos de uso reais

Módulos e encapsulamento

Closures são a base do padrão de módulo — criar estado privado sem depender de classes:

function criarCarrinho() {
  const itens = []; // privado

  return {
    adicionar(produto) {
      itens.push(produto);
    },
    total() {
      return itens.reduce((acc, item) => acc + item.preco, 0);
    },
    listar() {
      return [...itens]; // retorna cópia, não referência
    },
  };
}

const carrinho = criarCarrinho();
carrinho.adicionar({ nome: "Teclado", preco: 350 });
carrinho.adicionar({ nome: "Mouse", preco: 150 });
console.log(carrinho.total()); // 500

Ninguém de fora acessa itens diretamente. A closure garante isso.

Memoização simples

function memoizar(fn) {
  const cache = {}; // fecha sobre esse cache

  return function (...args) {
    const chave = JSON.stringify(args);
    if (cache[chave] !== undefined) {
      return cache[chave];
    }
    cache[chave] = fn(...args);
    return cache[chave];
  };
}

const fib = memoizar(function (n) {
  if (n <= 1) return n;
  return fib(n - 1) + fib(n - 2);
});

console.log(fib(40)); // rápido — resultados em cache

Partial application e currying

function multiplicar(a) {
  return function (b) {
    return a * b;
  };
}

const dobrar = multiplicar(2);
const triplicar = multiplicar(3);

console.log(dobrar(5));    // 10
console.log(triplicar(5)); // 15

Útil para criar variações de funções sem repetir lógica.

Closures e performance: o que monitorar

Closures mantêm referências ao escopo externo vivas. Se esse escopo contém objetos grandes, a memória não é liberada enquanto a closure existir.

// ❌ Problema em potencial
function processarDados(dados) {
  const resultadoGrande = dados.map(/* operação pesada */);

  return function relatorio() {
    // `resultadoGrande` nunca será coletado enquanto `relatorio` existir
    return resultadoGrande.length;
  };
}
// ✓ Captura só o que precisa
function processarDados(dados) {
  const resultadoGrande = dados.map(/* operação pesada */);
  const tamanho = resultadoGrande.length; // captura só o número

  return function relatorio() {
    return tamanho; // `resultadoGrande` pode ser coletado
  };
}

Na prática, esse problema aparece em aplicações que criam muitas closures com referências a dados grandes — workers, processamento batch, listeners que nunca são removidos. Nos casos do dia a dia, não tem de que se preocupar.

FAQ

Toda função em JavaScript é uma closure? Tecnicamente sim — toda função mantém uma referência ao escopo onde foi definida. Na prática, falamos de closure quando essa referência ao escopo externo é o que torna a função útil ou interessante. Uma função que não usa nenhuma variável do escopo externo forma uma closure vazia, sem efeito observável.

Closure é a mesma coisa que escopo léxico? Não exatamente. Escopo léxico é a regra que define quais variáveis uma função pode acessar com base em onde foi escrita — não onde é executada. Closure é o mecanismo que mantém esse escopo acessível mesmo depois que a função externa terminou. Um depende do outro, mas não são sinônimos.

Por que meu useEffect no React está usando um valor desatualizado? Stale closure. O efeito capturou um valor do estado em um render anterior e não foi re-executado quando o estado mudou. A solução quase sempre é adicionar a dependência correta no array de deps do useEffect, ou usar useRef para valores que precisam ser lidos sempre com o valor atual sem re-executar o efeito.

Closure tem custo de performance relevante? Em condições normais, não. O custo real aparece quando closures capturam referências a objetos grandes e impedem o garbage collector de agir — especialmente em loops que criam milhares de closures ou em listeners que nunca são removidos. Para o código de aplicação típico, esse custo é desprezível.

Quando devo preferir closure ao invés de uma classe? Quando você precisa de encapsulamento simples e não precisa de herança, polimorfismo ou instanciação múltipla com new. Módulos baseados em closure são mais simples de raciocinar, sem o this que muda de contexto dependendo de como a função é chamada.

Próximos passos

Closures são a base de vários padrões avançados em JavaScript. Agora que o conceito está claro, os próximos temas naturais são:

  • Currying e partial application — transformar funções de múltiplos argumentos em cadeias de funções unárias
  • Stale closures no React — por que o useEffect captura valores antigos e como resolver com useRef e dependências corretas
  • Module pattern — encapsulamento real sem TypeScript private ou # do ES2022
  • Memoização avançada — quando e como aplicar cache em funções puras sem biblioteca

Se quiser ver mais posts sobre fundamentos JavaScript aplicados na prática, o blog da Dev Code cobre event loop, promises, async/await e escopo com a mesma abordagem direta.