Closures: funções que lembram
Uma função criada dentro de outra não esquece as variáveis de fora quando a de fora termina. Essa memória chama-se closure, e ela resolve problemas que nenhuma outra ferramenta resolve tão bem.
Estudar esta lição interativa →Imagine que você precisa de um contador de cliques para cada botão de uma página, e cada um tem que guardar o seu próprio número, sem misturar com o dos outros. Ou um botão de "curtir" que lembra se você já curtiu aquele post específico. Dá para resolver isso com uma variável global para cada botão, mas isso não escala: e se a página tiver 50 posts? Cinquenta variáveis soltas, cada uma com um nome só para não colidir com as outras, é uma receita para bagunça. Existe um jeito melhor, e ele usa algo que você já viu de relance no Módulo 5: o escopo.
Você aprendeu que uma variável criada dentro de uma função só existe ali dentro e desaparece quando a função termina de rodar. Isso é verdade, com uma exceção enorme: se a função de dentro sobrevive à função de fora, por exemplo, porque ela é devolvida com return e guardada em algum lugar, ela carrega consigo o acesso às variáveis do escopo onde nasceu. Essa "memória" tem nome: closure (em português, algo como "fechamento").
Pense na closure como uma mochila que a função interna carrega consigo. Quando a função de fora que a criou termina de executar, o JavaScript normalmente jogaria fora todo aquele ambiente. Mas, se a função interna ainda está viva em algum lugar (guardada numa variável, passada adiante), ela leva na mochila uma referência às variáveis de que precisa. Enquanto alguém segurar essa função, a mochila continua ali, intacta.
Veja isso resolvendo exatamente o problema do contador independente. A função criarContador devolve outra função, que fecha sobre a variável contador. Cada chamada de criarContador() cria um escopo novinho, com o seu próprio contador, então dois contadores nunca se misturam:
// criarContador devolve uma FUNÇÃO, que fecha sobre "contador".
function criarContador() {
let contador = 0;
return function incrementar() {
contador++;
return contador;
};
}
const contadorA = criarContador();
const contadorB = criarContador();
console.log(contadorA()); // 1
console.log(contadorA()); // 2
console.log(contadorA()); // 3
console.log(contadorB()); // 1 (independente de contadorA!)Repare no que aconteceu: criarContador já terminou de rodar há muito tempo quando você chama contadorA() pela segunda vez. Por qualquer lógica ingênua, a variável contador deveria ter sumido junto. Mas ela não sumiu, porque a função incrementar (a que foi devolvida) continua com acesso a ela. É exatamente essa persistência que faz de contadorA e contadorB dois contadores totalmente independentes, mesmo vindo da mesma "receita".
Um erro comum é pensar que a função interna "fotografa" o valor da variável no instante em que foi criada. Não é isso: ela guarda uma referência viva. Se o valor da variável mudar depois (por outro código, ou pela própria função), a closure sempre vê o valor atual, nunca um congelado do passado. Essa diferença entre "referência" e "cópia" é o que explica o comportamento da próxima lição, e vale grudar na cabeça agora.
Closures não são um truque acadêmico: elas aparecem toda hora em código de produto. Um debounce de campo de busca (que espera você parar de digitar antes de buscar) guarda um temporizador numa closure. Um botão de "curtir" guarda o estado de curtido/não curtido sem precisar de uma variável global por post. Um gerador de ID único guarda um contador interno que só ele consegue incrementar. Em todos os casos, o padrão é o mesmo do exemplo acima: uma função de fora guarda uma variável "privada" e devolve funções que sabem mexer nela, sem expor a variável diretamente para quem está de fora.
// Sua vez! Complete criarContador (igual ao exemplo) e crie dois contadores.
function criarContador() {
let contador = 0;
return function incrementar() {
contador++;
return contador;
};
}
// Crie placarA e placarB, cada um chamando criarContador():
const placarA = criarContador();
const placarB = criarContador();
// Incremente placarA três vezes e placarB uma vez, depois mostre os dois:
placarA();
placarA();
placarA();
placarB();
console.log("placarA:", placarA());
console.log("placarB:", placarB());Crie dois contadores independentes e clique em Rodar.