$cd~/artigos/scope-and-closures-escopo-fechamentos-javascript
Fundamentals
série 33 Conceitos JS·parte 11 de 38

Scope and Closures: Onde as Variáveis Vivem e Como as Funções Lembram

14 minVanderlei José Alamino

Por que algumas variáveis podem ser acessadas de qualquer lugar do seu código, enquanto outras parecem desaparecer? Como as funções "lembram" variáveis de suas funções pai, mesmo depois que essas funções já terminaram de rodar?

snippetJavaScript
function createCounter() {  let count = 0;  // Esta variável está "fechada"   return function() {    count++;    return count;  };} const counter = createCounter();console.log(counter());  // 1console.log(counter());  // 2 — ela lembra!

As respostas estão em entender scope (escopo) e closures (fechamentos). Esses dois conceitos fundamentais governam como as variáveis funcionam no JavaScript. O escopo determina onde as variáveis são visíveis, enquanto os closures permitem que funções lembrem seu ambiente original.

Neste artigo, vamos desmontar cada tipo de escopo, como var, let e const se comportam de forma diferente, e por que closures são uma das ferramentas mais poderosas da linguagem.

scope-and-closures-escopo-fechamentos-javascript.meta.jsonjson

// metadados do artigo

{
  "tag": "fundamentos",
  "readTime": "14 min",
  "author": "Vanderlei José Alamino",
  "source": "33jsconcepts.com",
  "series": "33 Conceitos JS",
  "part": {
    "current": 11,
    "total": 38
  }
}
abrir fonte original
scope-and-closures-escopo-fechamentos-javascript.mdmarkdown

Fonte de referência

Conteúdo adaptado a partir do guia original em inglês.

33jsconcepts.com
01

seção

O Que é Escopo em JavaScript?

Escopo é o contexto atual de execução em que valores e expressões são "visíveis" ou podem ser referenciados. É o conjunto de regras que determina onde e como as variáveis podem ser acessadas no seu código. Se uma variável não está no escopo atual, ela não pode ser usada. Escopos podem ser aninhados, e escopos internos têm acesso a escopos externos, mas não vice-versa.

02

seção

O Prédio de Escritórios: Uma Analogia do Mundo Real

Imagine que, depois do expediente, você está no prédio de escritórios onde trabalha (legalmente — você trabalha lá, prometo). Você percebe algo interessante sobre o que consegue e não consegue ver:

  • Dentro do seu escritório privado, você vê tudo na sua mesa, espreita o corredor pela porta e até enxerga o saguão pelas paredes de vidro
  • No corredor, você vê o saguão claramente, mas os escritórios privados? As persianas estão fechadas. Sem espiar
  • No saguão, você está limitado ao que está ali: a recepção, algumas cadeiras, talvez uma planta meio triste
snippettext
┌─────────────────────────────────────────────────────────────┐│ SAGUÃO (Escopo Global)                                      ││   recepcao = "Mesa de Boas-Vindas"                          ││                                                             ││   ┌───────────────────────────────────────────────────┐     ││   │ CORREDOR (Escopo de Função)                       │     ││   │   plantaCorredor = "Samambaia"                    │     ││   │                                                   │     ││   │   ┌───────────────────────────────────────┐       │     ││   │   │ ESCRITÓRIO PRIVADO (Escopo de Bloco)  │       │     ││   │   │   docsSecretos = "Confidencial"       │       │     ││   │   │                                       │       │     ││   │   │   Pode ver: docsSecretos      ✓       │       │     ││   │   │   Pode ver: plantaCorredor  ✓       │       │     ││   │   │   Pode ver: recepcao          ✓       │       │     ││   │   └───────────────────────────────────────┘       │     ││   │                                                   │     ││   │   Não pode ver: docsSecretos       ✗              │     ││   └───────────────────────────────────────────────────┘     ││                                                             ││   Não pode ver: plantaCorredor, docsSecretos  ✗             │└─────────────────────────────────────────────────────────────┘

É exatamente assim que o escopo funciona no JavaScript! Código em escopos internos pode "olhar para fora" e acessar variáveis de escopos externos, mas escopos externos nunca podem "olhar para dentro" de escopos internos.

E aqui fica realmente interessante: imagine alguém que trabalhou naquele escritório privado, pediu demissão e saiu do prédio. Mas levou uma "foto mental" de tudo que estava lá — as senhas nos post-its, os planos secretos, a localização da gaveta de snacks. Mesmo tendo saído, essa pessoa ainda lembra de tudo. Isso é essencialmente o que um closure é: uma função que "lembra" o escopo onde foi criada, mesmo depois que esse escopo já se foi.

Por Que o Escopo Existe?

1. Prevenir Conflitos de Nomes

Sem escopo, toda variável seria global. Imagine o caos se cada i em cada loop for precisasse ter um nome único!

snippetJavaScript
function contarMacas() {  let count = 0;  // Este 'count' é separado...} function contarLaranjas() {  let count = 0;  // ...deste 'count'}

2. Gerenciamento de Memória

Quando um escopo termina, variáveis declaradas nele podem ser coletadas pelo garbage collector. Isso mantém seu programa eficiente.

snippetJavaScript
function processarDados() {  let arrayGigante = new Array(1000000);  // Consome memória  // ... processa}  // arrayGigante agora pode ser coletado

3. Encapsulamento e Segurança

Escopo permite esconder detalhes de implementação e proteger dados de modificação acidental.

snippetJavaScript
function criarContaBancaria() {  let saldo = 0;  // Privado! Não pode ser acessado diretamente   return {    depositar(valor) { saldo += valor; },    getSaldo() { return saldo; },  };}
03

seção

Os Três Tipos de Escopo

O JavaScript tem três tipos principais de escopo. Entender cada um é fundamental para escrever código previsível.

Nota: Módulos ES6 também introduzem escopo de módulo, onde variáveis de nível superior são escopadas ao módulo em vez de serem globais. Saiba mais em nosso guia de IIFE, Módulos e Namespaces.

1. Escopo Global

Variáveis declaradas fora de qualquer função ou bloco estão no escopo global. São acessíveis de qualquer lugar do seu código.

snippetJavaScript
// Escopo globalconst appName = "MeuApp";let userCount = 0; function greet() {  console.log(appName);  // ✓ Pode acessar variável global  userCount++;           // ✓ Pode modificar variável global} if (true) {  console.log(appName);  // ✓ Pode acessar variável global}

O Objeto Global

Em navegadores, variáveis globais com var viram propriedades do objeto window. No Node.js, vão para global. A forma moderna e universal de acessar o objeto global é globalThis.

snippetJavaScript
var antiga = "Estou no window";      // window.antiga (só var)let moderna = "NÃO estou no window";  // NÃO fica no window console.log(window.antiga);          // "Estou no window"console.log(window.moderna);          // undefinedconsole.log(globalThis);               // Funciona em todo lugar

Evite poluir o escopo global! Muitas variáveis globais levam a conflitos de nomes, bugs difíceis de rastrear e código difícil de manter.

2. Escopo de Função

Variáveis declaradas com var dentro de uma função são function-scoped. Só são acessíveis dentro dessa função.

snippetJavaScript
function calcularTotal() {  var subtotal = 100;  var tax = 10;  var total = subtotal + tax;   console.log(total);  // ✓ 110} calcularTotal();// console.log(subtotal);  // ✗ ReferenceError: subtotal is not defined

Hoisting com var

Variáveis declaradas com var são "içadas" (hoisted) para o topo da função. O JavaScript sabe que existem antes do código rodar, mas ficam undefined até a linha de declaração.

snippetJavaScript
function exemplo() {  console.log(message);  // undefined (não é erro!)  var message = "Hello";  console.log(message);  // "Hello"}
snippettext
Seu código:                    Como o JS interpreta:┌─────────────────────┐       ┌─────────────────────┐│ function foo() {    │       │ function foo() {    ││                     │       │   var x;  // içado  ││   console.log(x);   │  ──►  │   console.log(x);   ││   var x = 5;        │       │   x = 5;            ││ }                   │       │ }                   │└─────────────────────┘       └─────────────────────┘

3. Escopo de Bloco

Variáveis declaradas com let e const são block-scoped. Um bloco é qualquer código entre chaves {}: if, for, while ou blocos soltos.

snippetJavaScript
if (true) {  let blockLet = "Sou de bloco";  const blockConst = "Eu também";  var functionVar = "Eu escapo do bloco!";} // console.log(blockLet);    // ✗ ReferenceError// console.log(blockConst);  // ✗ ReferenceErrorconsole.log(functionVar);    // ✓ "Eu escapo do bloco!"

A Temporal Dead Zone (TDZ)

Diferente de var, variáveis com let e const não são inicializadas até sua declaração ser avaliada. Acessá-las antes causa ReferenceError. Esse período é a Temporal Dead Zone (Zona Morta Temporal).

snippetJavaScript
function demo() {  // TDZ de 'name' começa aqui   console.log(name);  // ReferenceError: Cannot access 'name' before initialization   let name = "Alice"; // TDZ termina aqui   console.log(name);  // "Alice"}
snippettext
┌────────────────────────────────────────────────────────────┐│                                                            ││   function demo() {                                        ││                                                            ││     ┌────────────────────────────────────────────────┐     ││     │           TEMPORAL DEAD ZONE                   │     ││     │                                                │     ││     │   'name' existe mas não pode ser acessado!     │     ││     │                                                │     ││     │   console.log(name);  // ReferenceError        │     ││     │                                                │     ││     └────────────────────────────────────────────────┘     ││                           │                                ││                           ▼                                ││     let name = "Alice";   // TDZ termina aqui              ││                                                            ││     console.log(name);    // "Alice" - funciona!           ││                                                            ││   }                                                        ││                                                            │└────────────────────────────────────────────────────────────┘

A TDZ existe para pegar erros de programação. É uma coisa boa! Ela impede que você use variáveis acidentalmente antes de estarem prontas.

04

seção

var vs let vs const

Aqui está uma comparação completa das três palavras-chave de declaração de variáveis:

Característicavarletconst
EscopoFunçãoBlocoBloco
HoistingSim (inicializa como undefined)Sim (mas TDZ)Sim (mas TDZ)
Redeclaração✓ Permitida✗ Erro✗ Erro
Reatribuição✓ Permitida✓ Permitida✗ Erro
Deve InicializarNãoNãoSim

Redeclaração

snippetJavaScript
// var permite redeclaração (pode causar bugs!)var name = "Alice";var name = "Bob";     // Sem erro, sobrescreve silenciosamenteconsole.log(name);    // "Bob" // let e const impedem redeclaraçãolet age = 25;// let age = 30;      // SyntaxError: 'age' has already been declared const PI = 3.14;// const PI = 3.14159 // SyntaxError

Reatribuição

snippetJavaScript
// var e let permitem reatribuiçãovar count = 1;count = 2;           // ✓ OK let score = 100;score = 200;         // ✓ OK // const impede reatribuiçãoconst API_KEY = "abc123";// API_KEY = "xyz789";  // TypeError: Assignment to constant variable // MAS: objetos/arrays const PODEM ser mutados!const user = { name: "Alice" };user.name = "Bob";   // ✓ Funciona!user.age = 25;       // ✓ Funciona também!// user = {};        // ✗ Falha (reatribuição)

Comportamento de Hoisting

snippetJavaScript
function hoistingDemo() {  // var: içado e inicializado como undefined  console.log(a);  // undefined  var a = 1;   // let: içado mas NÃO inicializado (TDZ)  // console.log(b);  // ReferenceError!  let b = 2;   // const: igual ao let  // console.log(c);  // ReferenceError!  const c = 3;}

O Problema Clássico do for-loop

Este é um dos gotchas mais comuns do JavaScript — e ilustra perfeitamente por que let é preferível a var:

snippetJavaScript
// O Problema: var é function-scopedfor (var i = 0; i < 3; i++) {  setTimeout(() => {    console.log(i);  }, 100);}// Saída: 3, 3, 3  (não 0, 1, 2!) // Por quê? Há apenas UMA variável 'i' compartilhada entre todas as iterações.// Quando os callbacks do setTimeout rodam, o loop já terminou e i === 3.
snippetJavaScript
// A Solução: let é block-scopedfor (let i = 0; i < 3; i++) {  setTimeout(() => {    console.log(i);  }, 100);}// Saída: 0, 1, 2  (correto!) // Por quê? Cada iteração recebe sua PRÓPRIA variável 'i'.// Cada callback do setTimeout fecha sobre um 'i' diferente.

Boas práticas modernas:

  1. Use const por padrão
  1. Use let quando precisar reatribuir
  1. Evite var completamente (só em código legado)
05

seção

Escopo Léxico

Escopo léxico (também chamado de escopo estático) significa que o escopo de uma variável é determinado pela sua posição no código-fonte, não por como as funções são chamadas em tempo de execução. Como Kyle Simpson explica em You Don't Know JS: Scope & Closures, o escopo léxico é determinado no "lex-time" — o momento em que o código está sendo analisado — por isso também é chamado de escopo "estático".

snippetJavaScript
const outer = "Estou fora!"; function outerFunction() {  const middle = "Estou no meio!";   function innerFunction() {    const inner = "Estou dentro!";     // innerFunction pode acessar as três variáveis    console.log(inner);   // ✓ Próprio escopo    console.log(middle);  // ✓ Escopo pai    console.log(outer);   // ✓ Escopo global  }   innerFunction();  // console.log(inner);  // ✗ ReferenceError} outerFunction();// console.log(middle);   // ✗ ReferenceError

A Cadeia de Escopo (Scope Chain)

Quando o JavaScript precisa encontrar uma variável, ele percorre a cadeia de escopo. Começa no escopo atual e sobe até encontrar a variável ou chegar ao escopo global.

Olhe no Escopo Atual

O JavaScript primeiro verifica se a variável existe no escopo da função/bloco atual.

Olhe no Escopo Pai

Se não encontrar, verifica o escopo externo (pai).

Continue Subindo a Cadeia

Esse processo continua por todos os escopos ancestrais.

Chegue ao Escopo Global

Por fim, verifica o escopo global. Se ainda não encontrar, lança ReferenceError.

snippettext
Busca de Variável: Onde está 'x'?┌─────────────────────────────────────────────────┐│ Escopo Global                                   ││   x = "global"                                  ││                                                 ││   ┌─────────────────────────────────────────┐   ││   │ Escopo outer()                          │   ││   │   x = "outer"                           │   ││   │                                         │   ││   │   ┌─────────────────────────────────┐   │   ││   │   │ Escopo inner()                  │   │   ││   │   │                                 │   │   ││   │   │   console.log(x);               │   │   ││   │   │         │                       │   │   ││   │   │         ▼                       │   │   ││   │   │   1. Verifica inner() → não achou│   │   ││   │   │         │                       │   │   ││   │   └─────────│───────────────────────┘   │   ││   │             ▼                           │   ││   │   2. Verifica outer() → ENCONTROU! "outer"│   ││   │                                         │   ││   └─────────────────────────────────────────┘   ││                                                 │└─────────────────────────────────────────────────┘Resultado: "outer"

Sombreamento de Variáveis (Variable Shadowing)

Quando um escopo interno declara uma variável com o mesmo nome de um escopo externo, a variável interna "sombreia" a externa:

snippetJavaScript
const name = "Global"; function greet() {  const name = "Função";  // Sombreia 'name' global   if (true) {    const name = "Bloco";   // Sombreia 'name' da função    console.log(name);      // "Bloco"  }   console.log(name);        // "Função"} greet();console.log(name);          // "Global"

Sombreamento pode confundir. Embora às vezes intencional, sombreamento acidental é fonte comum de bugs. Muitos linters alertam sobre isso.

06

seção

O Que é um Closure em JavaScript?

Um closure é a combinação de uma função empacotada junto com referências ao seu estado circundante (o ambiente léxico). Segundo a MDN, um closure dá a uma função acesso a variáveis de um escopo externo (enclosing), mesmo depois que a função externa terminou de executar e retornou. Toda função em JavaScript cria um closure no momento da criação.

Lembra da analogia do prédio de escritórios? Um closure é como alguém que trabalhou no escritório privado, saiu do prédio, mas ainda lembra exatamente onde estava tudo — e pode usar esse conhecimento!

Toda Função Cria um Closure

Em JavaScript, closures são criados automaticamente toda vez que você cria uma função. A função mantém uma referência ao seu ambiente léxico.

snippetJavaScript
function createGreeter(greeting) {  // 'greeting' está no escopo de createGreeter   return function(name) {    // Esta função interna é um closure!    // Ela "fecha sobre" a variável 'greeting'    console.log(`${greeting}, ${name}!`);  };} const sayHello = createGreeter("Hello");const sayHola = createGreeter("Hola"); // createGreeter já terminou de executar, mas...sayHello("Alice");  // "Hello, Alice!"sayHola("Bob");     // "Hola, Bob!" // As funções internas ainda lembram seus valores de 'greeting'!

Como Closures Funcionam: Passo a Passo

A Função Externa é Chamada

createGreeter("Hello") é chamada. Um novo contexto de execução é criado com greeting = "Hello".

A Função Interna é Criada

A função interna é criada. Ela captura uma referência ao ambiente léxico atual (que inclui greeting).

A Função Externa Retorna

createGreeter retorna a função interna e seu contexto de execução é (normalmente) limpo.

Mas o Closure Sobrevive!

Como a função interna mantém referência ao ambiente léxico, a variável greeting NÃO é coletada pelo garbage collector. Ela sobrevive!

O Closure é Invocado Depois

Quando sayHello("Alice") é chamada, a função ainda pode acessar greeting através do seu closure.

snippettext
Depois que createGreeter("Hello") retorna:┌──────────────────────────────────────┐│ sayHello (Função)                    │├──────────────────────────────────────┤│ [[Code]]: function(name) {...}       ││                                      ││ [[Environment]]: ────────────────────────────┐└──────────────────────────────────────┘   │                                           ▼                          ┌────────────────────────────┐                          │ Ambiente Léxico            │                          │ (Mantido vivo pelo closure!)│                          ├────────────────────────────┤                          │ greeting: "Hello"          │                          └────────────────────────────┘
07

seção

Closures na Prática

Closures não são só teoria. Você os usa todo dia. Aqui estão os padrões que tornam closures tão poderosos.

1. Privacidade de Dados e Encapsulamento

Closures permitem criar variáveis verdadeiramente privadas em JavaScript:

snippetJavaScript
function createCounter() {  let count = 0;  // Variável privada — sem acesso direto!   return {    increment() {      count++;      return count;    },    decrement() {      count--;      return count;    },    getCount() {      return count;    },  };} const counter = createCounter(); console.log(counter.getCount());  // 0console.log(counter.increment()); // 1console.log(counter.increment()); // 2console.log(counter.decrement()); // 1 // NÃO há como acessar 'count' diretamente!console.log(counter.count);       // undefined

Este padrão é a fundação do Module Pattern, amplamente usado antes dos módulos ES6 se tornarem disponíveis. Saiba mais em nosso guia de IIFE, Módulos e Namespaces.

2. Fábricas de Funções

Closures permitem criar funções especializadas sob demanda:

snippetJavaScript
function createMultiplier(multiplier) {  return function(number) {    return number * multiplier;  };} const double = createMultiplier(2);const triple = createMultiplier(3);const tenX = createMultiplier(10); console.log(double(5));   // 10console.log(triple(5));   // 15console.log(tenX(5));     // 50 // Cada função "lembra" seu próprio multiplier

Este padrão funciona muito bem com a Fetch API para criar clientes de API reutilizáveis:

snippetJavaScript
// Exemplo do mundo real: fábricas de requisições de APIfunction createApiClient(baseUrl) {  return {    get(endpoint) {      return fetch(`${baseUrl}${endpoint}`);    },    post(endpoint, data) {      return fetch(`${baseUrl}${endpoint}`, {        method: 'POST',        body: JSON.stringify(data)      });    }  };} const githubApi = createApiClient('https://api.github.com');const myApi = createApiClient('https://myapp.com/api'); // Cada cliente lembra seu baseUrlgithubApi.get('/users/leonardomso');myApi.get('/users/1');

3. Preservando Estado em Callbacks e Event Handlers

Closures são essenciais para manter estado em código assíncrono. Quando você usa addEventListener() para anexar event handlers, esses handlers podem fechar sobre variáveis de seu escopo externo:

snippetJavaScript
function setupClickCounter(buttonId) {  let clicks = 0;  // Esta variável persiste entre cliques!   const button = document.getElementById(buttonId);   button.addEventListener('click', function() {    clicks++;    console.log(`Botão clicado ${clicks} vez${clicks === 1 ? '' : 'es'}`);  });} setupClickCounter('myButton');// Clique 1: "Botão clicado 1 vez"// Clique 2: "Botão clicado 2 vezes"// Clique 3: "Botão clicado 3 vezes"

4. Memoização (Cache de Resultados)

Closures permitem cache eficiente de computações caras:

snippetJavaScript
function createMemoizedFunction(fn) {  const cache = {};  // Cache persiste entre chamadas!   return function(arg) {    if (arg in cache) {      console.log('Retornando resultado em cache');      return cache[arg];    }     console.log('Computando resultado');    const result = fn(arg);    cache[arg] = result;    return result;  };} function factorial(n) {  if (n <= 1) return 1;  return n * factorial(n - 1);} const memoizedFactorial = createMemoizedFunction(factorial); console.log(memoizedFactorial(5));  // Computando resultado → 120console.log(memoizedFactorial(5));  // Retornando resultado em cache → 120
08

seção

Erros e Armadilhas Comuns

Entender escopo e closures significa entender onde as coisas dão errado. Estes são os erros que pegam até desenvolvedores experientes.

A Pergunta #1 de Entrevista sobre Closures

Esta é a armadilha clássica de closure. Quase todo mundo erra na primeira vez:

O Problema

snippetJavaScript
// O que isso imprime?for (var i = 0; i < 3; i++) {  setTimeout(function() {    console.log(i);  }, 1000);} // A maioria espera: 0, 1, 2// Saída real: 3, 3, 3

Por Que Isso Acontece?

snippettext
O que realmente acontece:  TEMPO ════════════════════════════════════════════════════►  ┌─────────────────────────────────────────────────────────┐  │ IMEDIATAMENTE (milissegundos):                          │  │                                                         │  │   Iteração 1 do loop: i = 0, agenda callback           │  │   Iteração 2 do loop: i = 1, agenda callback           │  │   Iteração 3 do loop: i = 2, agenda callback           │  │   Loop termina: i = 3                                   │  │                                                         │  │   Os 3 callbacks apontam para a MESMA variável 'i' ──┐  │  └──────────────────────────────────────────────────────│──┘                                                         │                                                         ▼  ┌─────────────────────────────────────────────────────────┐  │ ~1 SEGUNDO DEPOIS:                                      │  │                                                         │  │   callback1 roda: "Qual é i?" → i é 3 → imprime 3       │  │   callback2 roda: "Qual é i?" → i é 3 → imprime 3       │  │   callback3 roda: "Qual é i?" → i é 3 → imprime 3       │  │                                                         │  └─────────────────────────────────────────────────────────┘  Resultado: 3, 3, 3 (não 0, 1, 2!)

As Soluções

Solução 1: Use let

A solução moderna mais simples. let cria um novo binding para cada iteração:

snippetJavaScript
for (let i = 0; i < 3; i++) {  setTimeout(function() {    console.log(i);  }, 1000);}// Saída: 0, 1, 2 ✓

Solução 2: IIFE

Solução pré-ES6 usando Immediately Invoked Function Expression:

snippetJavaScript
for (var i = 0; i < 3; i++) {  (function(j) {    setTimeout(function() {      console.log(j);    }, 1000);  })(i);  // Passa i como argumento, criando um novo 'j' cada vez}// Saída: 0, 1, 2 ✓

Solução 3: forEach

Usando métodos de array, que naturalmente criam novo escopo por iteração:

snippetJavaScript
[0, 1, 2].forEach(function(i) {  setTimeout(function() {    console.log(i);  }, 1000);});// Saída: 0, 1, 2 ✓

Vazamentos de Memória com Closures

Closures são poderosos, mas vêm com responsabilidade. Como closures mantêm referências às variáveis do escopo externo, essas variáveis não podem ser coletadas pelo garbage collector.

Vazamentos de Memória Potenciais

snippetJavaScript
function createHeavyClosure() {  const hugeData = new Array(1000000).fill('x');  // Dados grandes   return function() {    // Esta referência a hugeData mantém o array inteiro na memória    console.log(hugeData.length);  };} const leakyFunction = createHeavyClosure();// hugeData ainda está na memória porque o closure referencia

Engines modernos como o V8 podem otimizar closures que não usam variáveis externas. Porém, é boa prática assumir que variáveis referenciadas são retidas e limpar dados grandes explicitamente quando terminar.

Quebrando Referências de Closure

Quando terminar com um closure, quebre a referência explicitamente. Use removeEventListener() para limpar event handlers:

snippetJavaScript
function setupHandler(element) {  const largeData = { users: new Array(10000).fill({ name: 'User' }) };   const handler = function() {    console.log(`Processando ${largeData.users.length} usuários`);  };   element.addEventListener('click', handler);   // Retorna função de cleanup  return function cleanup() {    element.removeEventListener('click', handler);    // Agora handler e largeData podem ser coletados  };} const button = document.getElementById('myButton');const cleanup = setupHandler(button); // Depois, quando não precisar mais:cleanup();  // Remove listener, libera memória

Boas práticas:

  1. Não capture mais do que precisa em closures
  1. Defina referências de closure como null quando terminar
  1. Remova event listeners quando componentes desmontarem
  1. Tenha cuidado especial em loops e aplicações de longa duração
09

seção

Teste Seu Conhecimento

$ summary --quick

Resumo rápido

  • Escopo = Visibilidade de Variáveis — Determina onde variáveis podem ser acessadas
  • Três tipos de escopo — Global (em todo lugar), Função (var), Bloco (let/const)
  • Escopo léxico é estático — Determinado pela posição no código, não pelo comportamento em runtime
  • Cadeia de escopo — JavaScript busca variáveis do escopo interno para o externo
  • Prefira let e const — São block-scoped; evite var
  • Temporal Dead Zone — let/const não podem ser acessados antes da declaração
  • Closure = Função + Ambiente Léxico — Funções "lembram" onde foram criadas
  • Closures habilitam — Privacidade de dados, fábricas de funções, callbacks com estado, memoização
  • Cuidado com o gotcha do loop — Use let em vez de var em loops com callbacks assíncronos
  • Atenção à memória — Closures mantêm referências vivas; faça cleanup quando terminar

Créditos da série

Este artigo faz parte de uma série de estudos inspirada no projeto 33 Conceitos JS, criado por Leonardo Maldonado. Todo o conteúdo original está disponível em 33jsconcepts.com.