Closures e Escopo em JavaScript Explicados de Forma Simples

Frontend2026-09-19TryQuickToolBox

Você está escrevendo uma função JavaScript que retorna outra função. A função interna usa uma variável da função externa, mas quando você a chama depois, a função externa já terminou. No entanto, a variável ainda está lá. Como? Essa é a magia das closures e do escopo. Muitos desenvolvedores lutam com esse conceito, levando a bugs como valores inesperados de variáveis ou vazamentos de memória. Neste artigo, vamos desmistificar closures e escopo em JavaScript de forma simples com exemplos práticos.

O que é Escopo em JavaScript?

O escopo determina onde as variáveis são acessíveis no seu código. JavaScript tem três tipos principais de escopo:

Aqui está um exemplo rápido:

let globalVar = 'I am global';

function myFunc() {
  let functionVar = 'I am in function scope';
  if (true) {
    let blockVar = 'I am in block scope';
    console.log(globalVar); // accessible
    console.log(functionVar); // accessible
    console.log(blockVar); // accessible
  }
  console.log(blockVar); // ReferenceError: blockVar is not defined
}

myFunc();
console.log(functionVar); // ReferenceError: functionVar is not defined

Entender o escopo é crucial porque evita colisões de variáveis e ajuda a gerenciar a memória.

Escopo Lexical: A Base das Closures

JavaScript usa escopo lexical, o que significa que o escopo de uma variável é determinado por sua posição no código-fonte. Funções internas têm acesso a variáveis em seu escopo externo (envolvente). Isso também é chamado de escopo estático.

Considere isto:

function outer() {
  let outerVar = 'outer';
  function inner() {
    console.log(outerVar); // 'outer'
  }
  inner();
}
outer();

A função inner pode acessar outerVar porque está lexicalmente dentro de outer. Isso não é dinâmico; não depende de como a função é chamada.

O que é uma Closure?

Uma closure é uma função que lembra seu escopo lexical mesmo quando a função é executada fora desse escopo. Em outras palavras, uma closure dá acesso ao escopo de uma função externa a partir de uma função interna. Closures são criadas toda vez que uma função é criada, no momento da criação da função.

Exemplo:

function makeCounter() {
  let count = 0;
  return function() {
    count++;
    return count;
  };
}

const counter = makeCounter();
console.log(counter()); // 1
console.log(counter()); // 2

Aqui, makeCounter retorna uma função interna que incrementa count. Mesmo depois que makeCounter terminou de executar, a função retornada ainda tem acesso a count. Isso é uma closure.

Como as Closures Funcionam nos Bastidores

Quando uma função é criada, ela armazena uma referência ao seu ambiente lexical (a cadeia de escopo). Quando a função é invocada, ela usa esse ambiente para resolver variáveis. O ambiente é mantido vivo enquanto a closure existir.

Usos Práticos de Closures

Closures não são apenas teóricas; elas são usadas em toda parte no JavaScript.

1. Privacidade de Dados (Padrão Módulo)

Antes dos módulos ES6, closures eram usadas para criar variáveis privadas:

const counterModule = (function() {
  let count = 0;
  return {
    increment: function() { count++; },
    getCount: function() { return count; }
  };
})();

counterModule.increment();
console.log(counterModule.getCount()); // 1
console.log(counterModule.count); // undefined

Aqui, count é privada e acessível apenas via os métodos retornados.

2. Manipuladores de Eventos e Callbacks

Closures são comumente usadas em manipuladores de eventos para acessar variáveis do escopo externo:

function setupButton() {
  let clickCount = 0;
  document.getElementById('myButton').addEventListener('click', function() {
    clickCount++;
    console.log(`Button clicked ${clickCount} times`);
  });
}

A closure do manipulador de eventos lembra clickCount.

3. Fábricas de Funções

Você pode criar funções com argumentos predefinidos:

function multiplyBy(factor) {
  return function(number) {
    return number * factor;
  };
}

const double = multiplyBy(2);
console.log(double(5)); // 10

Armadilhas Comuns e Como Evitá-las

Closures podem levar a comportamentos inesperados se não forem compreendidas.

Captura de Variável em Loop

Usar var em loops cria uma única ligação compartilhada:

for (var i = 0; i < 3; i++) {
  setTimeout(function() {
    console.log(i); // prints 3, 3, 3
  }, 100);
}

Todos os callbacks compartilham o mesmo i. Para corrigir, use let (escopo de bloco) ou uma IIFE:

for (let i = 0; i < 3; i++) {
  setTimeout(function() {
    console.log(i); // prints 0, 1, 2
  }, 100);
}

Vazamentos de Memória

Closures mantêm variáveis vivas. Se uma closure estiver anexada a um objeto de longa duração (como um elemento DOM), ela pode impedir a coleta de lixo. Sempre remova os ouvintes de eventos quando não forem mais necessários.

Cadeia de Escopo e Resolução de Variáveis

Quando você acessa uma variável, o JavaScript procura na cadeia de escopo: primeiro o escopo atual, depois os escopos externos, até o escopo global. Se não for encontrada, um ReferenceError é lançado.

Exemplo:

let a = 'global';
function outer() {
  let b = 'outer';
  function inner() {
    let c = 'inner';
    console.log(a, b, c); // all accessible
  }
  inner();
}
outer();

A cadeia de escopo para inner é: inner -> outer -> global.

Comparação: var, let e const

Palavra-chaveEscopoHoistingReatribuível
varFunçãoSim (inicializado como undefined)Sim
letBlocoSim (mas não inicializado, TDZ)Sim
constBlocoSim (mas não inicializado, TDZ)Não

Entender essas diferenças é fundamental para evitar bugs relacionados a escopo.

Perguntas Frequentes

O que é uma closure em JavaScript?

Uma closure é uma função que mantém acesso ao seu escopo lexical mesmo quando executada fora desse escopo. Ela permite que uma função "lembre" variáveis de onde foi definida.

Por que as closures são importantes?

Closures possibilitam privacidade de dados, fábricas de funções e manipuladores de eventos que mantêm estado. Elas são fundamentais para muitos padrões JavaScript.

Como posso evitar bugs relacionados a closures?

Use let e const para escopo de bloco, esteja atento a loops e limpe os ouvintes de eventos para evitar vazamentos de memória.

Pronto para testar seu entendimento? Experimente nosso Formatador JSON para experimentar trechos de código e ver como as closures funcionam em tempo real.