Menu

Closures em JavaScript: o guia definitivo com exemplos

Closure é a função que lembra das variáveis ao seu redor. Veja como closures funcionam em JavaScript, com exemplos práticos e casos de uso reais.

Closure em JavaScript é uma função que lembra

Toda vez que você define uma função em JavaScript, ela guarda silenciosamente uma referência às variáveis que existiam ao seu redor. Quando essa função é executada depois — mesmo em um lugar completamente diferente — ela ainda consegue enxergar essas variáveis. Isso é um closure.

A demonstração mais curta possível:

index.js
Output
Click Run to see the output here.

makeGreeter executa, devolve uma função interna e encerra. Você esperaria que a variável local name sumisse — afinal, a função já terminou. Mas a função interna ainda usa name, então o JavaScript mantém essa variável viva. greetAda lembra de "Ada". greetBoris lembra de "Boris". Dois closures, dois valores lembrados separadamente.

Escopo léxico: a origem dos closures em JavaScript

A regra por trás dos closures tem nome: escopo léxico. Uma função enxerga as variáveis do lugar onde ela foi escrita, e não do lugar onde é chamada. "Léxico" é só uma forma chique de dizer "baseado na posição dela no código-fonte".

index.js
Output
Click Run to see the output here.

show mostra "Estou fora" no console, e não "Estou dentro de caller". Ela foi escrita ao lado do outer do escopo global, então é esse que ela enxerga. Não importa se você chama a função a partir de um lugar que também tem seu próprio outer.

Closures nada mais são do que escopo léxico que sobrevive à função externa. A variável não some porque ainda tem alguém segurando uma referência a ela.

Cada chamada cria seu próprio closure

Uma nova chamada da função externa cria variáveis novas, e qualquer função interna devolvida por essa chamada lembra daquelas variáveis específicas. É por isso que greetAda e greetBoris ali em cima não se atrapalharam.

O exemplo clássico disso é um contador com closure:

index.js
Output
Click Run to see the output here.

a e b têm, cada um, seu próprio count. Nada fora da função retornada consegue mexer nessas variáveis — o count é totalmente privado. E olha que legal: isso não é uma feature que a gente "ligou" na linguagem, é uma consequência natural de como os closures funcionam.

Variáveis privadas em JavaScript sem usar classes

Como as variáveis capturadas só podem ser acessadas através da função retornada, dá pra usar closures para montar pequenos objetos com um estado realmente privado:

index.js
Output
Click Run to see the output here.

balance não é uma propriedade do objeto retornado — ela vive dentro da closure. A única forma de ler ou alterar esse valor é pelos métodos que você expôs. Classes com campos #private também dão conta disso, mas a versão com closure é mais antiga por várias décadas e continua aparecendo em todo canto do ecossistema.

A pegadinha clássica do loop

As closures costumam confundir mais quando aparecem dentro de loops. Veja o que acontece quando você usa var:

index.js
Output
Click Run to see the output here.

Você esperava 0, 1, 2, mas recebeu 3, 3, 3. O motivo é o seguinte: var tem escopo de função, então existe apenas um i para o loop inteiro. As três closures capturaram a mesma variável e, quando elas finalmente executam, o loop já terminou e i vale 3.

Troque por let:

index.js
Output
Click Run to see the output here.

Agora o console exibe 0, 1, 2. O let tem escopo de bloco — a cada iteração do loop, uma nova ligação para i é criada, então cada closure captura seu próprio valor. Esse é, disparado, o maior motivo para preferir let em vez de var.

Closures capturam variáveis, não valores

Aqui vai um detalhe sutil, mas fundamental: uma closure guarda a variável em si, e não uma foto do valor que ela tinha no momento em que a função foi definida.

index.js
Output
Click Run to see the output here.

printMessage lê a variável message na hora em que é executada, e não no momento em que foi criada. Se você quer um snapshot do valor, copie-o para uma variável local antes — que é basicamente o que o let faz dentro de um loop for.

Um padrão clássico do dia a dia: a função Once

Aqui vai um pequeno utilitário que usa closure para garantir que uma função rode uma única vez:

index.js
Output
Click Run to see the output here.

called e result são estado privado que vive enquanto a função retornada existir. Sem flag global, sem objeto extra. Esse padrão — função auxiliar pequena, estado privado e closure — é uma das coisas mais úteis que o JavaScript oferece.

Uma palavrinha sobre memória

Uma closure mantém vivas as variáveis que capturou enquanto alguma coisa ainda referenciar a closure. Normalmente é exatamente isso que você quer, mas cuidado: se você anexa uma closure a algo de vida longa (tipo um listener de evento no DOM ou um cache global) e ela captura algo grande, esse "algo grande" não vai pro garbage collector enquanto a closure estiver por aí.

function attach() {
    const hugeData = new Array(1_000_000).fill("...");
    document.addEventListener("click", () => {
        console.log(hugeData.length);
    });
}

Enquanto o listener estiver ativo, hugeData continua na memória. Remova o listener (ou evite capturar o que você não precisa) e a referência é liberada. Não precisa ficar obcecado com isso — só tenha em mente que closures e memória andam de mãos dadas.

O que levar dessa conversa

  • Uma closure é uma função somada às variáveis que ela enxergava no momento em que foi definida.
  • Cada chamada à função externa cria um novo conjunto de variáveis para as closures internas.
  • Closures permitem ter estado privado sem precisar recorrer a classes.
  • Dentro de loops, use let para que cada iteração tenha seu próprio binding.
  • Closures capturam a variável em si, não o valor que ela tinha na hora da criação.

A seguir: a palavra-chave this

Closures cuidam das variáveis ao redor de uma função. A próxima peça do quebra-cabeça é sobre o que uma função é chamada — algo que, em JavaScript, é controlado pelo this e se comporta de um jeito bem diferente das variáveis capturadas que acabamos de ver.

Perguntas frequentes

O que é uma closure em JavaScript?

Closure é uma função que continua lembrando das variáveis do escopo onde foi criada, mesmo depois que esse escopo externo já terminou de executar. Na prática, toda função em JavaScript é uma closure — o termo costuma aparecer quando a função é retornada ou passada para outro lugar e ainda acessa variáveis do escopo original.

Para que servem as closures?

Elas permitem que uma função carregue um estado privado junto dela. Você consegue associar dados a uma função sem precisar recorrer a classes ou variáveis globais. Os usos mais comuns são contadores, callbacks que rodam uma única vez, funções memoizadas e esconder detalhes de implementação atrás de uma API enxuta.

Por que closures se comportam de forma estranha em loops com var?

O var tem escopo de função, então todas as iterações compartilham a mesma variável. As closures criadas dentro do loop acabam referenciando essa variável única, que já está no valor final quando elas finalmente executam. A solução é usar let, que tem escopo de bloco e cria um novo binding para cada iteração.

Aprenda a programar com o Coddy

COMEÇAR