Executando verificação de segurança...
9

Closures, um dos conceitos mais avançados do JavaScript

Se você trabalha com JavaScript há algum tempo, provavelmente já se deparou com closures — talvez até sem saber. Elas aparecem em entrevistas técnicas, em code reviews, e estão por trás de patterns que usamos todos os dias. Mas entender por que elas existem e como o motor JavaScript as gerencia na memória faz toda a diferença entre usar closures por acidente e usá-las com intenção.


O que é uma Closure?

De forma direta: uma closure é uma função que retém acesso às variáveis do escopo em que foi criada, mesmo depois que esse escopo já foi destruído.

Quando uma função é definida dentro de outra função e faz referência a variáveis do escopo externo, o motor JavaScript preserva essas variáveis na memória (Heap) através de uma referência interna chamada [[Scope]]. Isso significa que, mesmo depois que a função externa terminou de executar e seu Contexto de Execução foi removido da Call Stack, a função interna continua tendo acesso àquelas variáveis.


O exemplo: Memoization via Closure

Para tornar a explicação concreta, vamos analisar um pattern real e muito usado em produção — memoization. A ideia é simples: armazenar resultados de cálculos já realizados para evitar recomputação.

O código que vamos acompanhar é este:

function memoize(fn) {
  const cache = {};
  return function(n) {
    if (cache[n] !== undefined) {
      console.log("cached: " + n);
      return cache[n];
    }
    const result = fn(n);
    cache[n] = result;
    return result;
  };
}

function square(x) { return x * x; }

const memoSquare = memoize(square);
console.log(memoSquare(4));  // 16  (computed)
console.log(memoSquare(4));  // cached: 4 → 16
console.log(memoSquare(5));  // 25  (computed)

Passo a passo visual

1. O estado inicial — Declaração das funções

image

O motor JavaScript começa a executar o script. O Contexto de Execução Global é criado e empilhado na Call Stack (painel central). A memória global (painel direito) está inicializada, mas ainda vazia.


2. cache = {} dentro de memoize

image

Quando memoize(square) é chamada (linha 17), um novo Contexto de Execução é empilhado. Dentro dele, a variável cache é declarada como um objeto vazio {} e armazenada no Heap. O parâmetro fn aponta para a função square. Essas duas variáveis — cache e fn — são as que a closure vai "capturar".


3. A Closure nasce

image

A função memoize retornou. Observe o que aconteceu: o Contexto de Execução de memoize foi removido da Call Stack. Em teoria, as variáveis locais cache e fn deveriam ter sido descartadas. Mas não foram. A memória local foi removida, mas o [[Scope]] da função interna retém as variáveis capturadas, mantendo esses vínculos vivos no Heap mesmo após o frame externo ter sido removido. Isso é a closure em ação.


4. memoSquare — a closure armazenada

image

Agora memoSquare existe na memória global e aponta para a função interna function(n). No Heap, essa função carrega consigo todo o escopo capturado: tanto a referência a fn (que aponta para square) quanto a referência a cache (o objeto {}). Essa "mochila de variáveis" é a closure.


5. Primeira chamada: memoSquare(4) — computando e armazenando

image

Quando memoSquare(4) é chamada, a função interna executa. Ela primeiro verifica cache[4] !== undefined — como o cache está vazio, a condição é false. O cálculo fn(4) é executado (ou seja, square(4) = 16), e o resultado é armazenado com cache[4] = 16. A closure permitiu que a função acessasse e modificasse o objeto cache que pertence ao escopo de uma função que já terminou de executar.


6. Segunda chamada: memoSquare(4) — cache hit!

image

Na segunda chamada com o mesmo argumento 4, a condição cache[n] !== undefined agora é true. O valor 16 está no cache. A função nem precisa chamar square novamente — retorna direto do cache. A barra inferior confirma a avaliação: o motor entra no bloco if.


7. O resultado no Console — prova do cache funcionando

image

O console agora mostra > 16 (primeira chamada, computada) e > cached: 4 (segunda chamada, do cache). A closure manteve o estado entre chamadas de forma completamente encapsulada.


8. Estado final — o cache acumulado

image

Execução concluída. O console mostra todos os resultados: 16, cached: 4, 16, 25. No Heap, o objeto cache agora contém {4: 16, 5: 25}. Cada chamada alimentou o cache, e a closure garantiu que esse estado persistisse entre todas as invocações.


Para que servem Closures na prática?

O exemplo de memoization é apenas um dos muitos patterns que se apoiam em closures. Na prática do dia a dia, closures são a base de diversas construções fundamentais do JavaScript:

Encapsulamento e estado privado. Closures permitem criar variáveis que não são acessíveis de fora, simulando o conceito de propriedades privadas antes mesmo de termos #privateFields nas classes.

Currying e aplicação parcial. Funções que retornam funções com parâmetros pré-configurados dependem inteiramente de closures para reter esses parâmetros.

Callbacks e event handlers. Toda vez que você passa uma callback que faz referência a uma variável do escopo externo, você está usando closures. Isso é especialmente comum em operações assíncronas, timers e listeners de eventos.

Module pattern. Antes dos ES Modules, o padrão de módulo em JavaScript era construído inteiramente sobre IIFEs e closures, permitindo expor apenas uma API pública enquanto o restante permanecia privado.

Hooks do React. Se você trabalha com React, useState, useEffect e todos os demais hooks dependem de closures para associar estado e efeitos ao componente correto.


O takeaway

Todo o passo a passo que mostrei aqui foi feito usando o JavaScript Visualized (javascriptvisualized.com), uma ferramenta interativa e visual que mostra em tempo real como o motor JavaScript executa seu código. Você pode ver a Call Stack empilhando e desempilhando, as Web APIs processando timers, as filas de microtasks e macrotasks sendo drenadas — tudo com animações e explicações a cada passo.

Carregando publicação patrocinada...
1

Apesar de ser interessante, closure cria mais problemas do que resolve;
quem nunca ficou p*** da vida com um valor que nao batia e depois descobriu que a funcao estava processando valores cacheados ?
uma delicia pegar codigo dos outros para manutencao;