Closures
Aprenda closures em JavaScript: como uma função memoriza seu escopo lexical, com exemplos de privacidade de dados, fábricas de funções, o padrão módulo e armadilhas comuns em loops.
Entendendo Closures em JavaScript
Em JavaScript, closures são um conceito fundamental e poderoso que permite a criação de funções com dados preservados de seu ambiente lexical. Este guia oferece uma exploração aprofundada de closures, com explicações detalhadas e exemplos práticos de código para solidificar o seu entendimento.
O Que é uma Closure?
Uma closure é a combinação de uma função e o ambiente lexical dentro do qual essa função foi declarada. Esse ambiente consiste em quaisquer variáveis locais que estavam no escopo no momento em que a closure foi criada.
Exemplo de uma Closure
Considere o seguinte exemplo:
Neste exemplo, greetByName é uma closure. Ela captura as variáveis greeting e name do escopo da função greet.
Quando a função greet é chamada com o argumento 'John', ela cria uma variável local greeting e uma função greetByName. A função greet então retorna a função greetByName. Mesmo após a função greet ter terminado de executar, a função greetByName retornada ainda tem acesso às variáveis greeting e name. Isso ocorre porque greetByName é uma closure, o que significa que ela memoriza o ambiente onde foi criada.
Por Que Usar Closures?
Closures permitem o encapsulamento de dados e a criação de variáveis privadas. Elas permitem que funções retenham acesso a variáveis de seu escopo externo (envolvente), mesmo após esse escopo ter terminado de executar.
Criando uma Closure Básica
Considere o seguinte exemplo:
Quando outerFunction é invocada, ela cria outerVariable e innerFunction. Em seguida, retorna innerFunction. A innerFunction retornada mantém acesso a outerVariable mesmo após outerFunction ter terminado de executar.
Casos de Uso Práticos de Closures
Privacidade de Dados
Closures podem ser usadas para criar dados privados que não podem ser acessados do escopo global.
Neste exemplo, count é uma variável privada que só pode ser acessada e modificada pela função anônima retornada. Cada vez que a função é chamada, ela incrementa e retorna o count atualizado.
Um ponto sutil, mas importante: cada chamada a createCounter produz sua própria closure independente com seu próprio count privado. Dois contadores criados separadamente não compartilham estado:
Cada invocação da função externa cria um ambiente lexical totalmente novo, portanto a e b incrementam variáveis separadas.
Fábricas de Funções
Closures podem ser usadas para criar fábricas de funções que geram funções especializadas.
Aqui, createMultiplier gera funções que multiplicam um dado número por um multiplier especificado. Cada função retornada mantém acesso ao valor de multiplier por meio da closure.
Closures em Loops
Closures podem ser complicadas quando usadas dentro de loops. Considere o seguinte exemplo:
Neste código, o loop é concluído e, em seguida, todos os três callbacks do setTimeout são executados, registrando o valor de i, que é 4 após o término do loop. Isso acontece porque var tem escopo de função.
Para corrigir isso, use let, que tem escopo de bloco:
Sempre use let ou const em vez de var para evitar problemas de escopo e garantir que as variáveis tenham o escopo apropriado.
Por Que a Versão com let Funciona
A chave é que let cria um novo binding de i para cada iteração do loop. Cada callback do setTimeout fecha sobre um i diferente, então ele memoriza o valor que existia durante sua própria iteração. Com var, há apenas um i compartilhado por todos os callbacks e, quando os temporizadores disparam, o loop já o incrementou para 4.
Antes de let existir, a solução clássica era criar um escopo fresco por iteração com uma IIFE:
A função invocada imediatamente copia o valor atual de i para seu próprio parâmetro captured, dando a cada callback uma cópia privada sobre a qual fechar.
Closures e Gerenciamento de Memória
Closures podem causar vazamentos de memória se não forem usadas adequadamente. Como closures mantêm referências ao seu escopo externo, variáveis não utilizadas podem persistir na memória por mais tempo do que o necessário.
Esteja atento ao uso de memória ao criar closures. Certifique-se de que as closures não capturem desnecessariamente objetos grandes ou elementos DOM para evitar vazamentos de memória.
Padrões Avançados de Closure
Padrão Módulo
O padrão módulo utiliza closures para criar membros privados e públicos dentro de uma função.
Neste exemplo, CounterModule é uma expressão de função invocada imediatamente (IIFE) que retorna um object com métodos públicos (increment e reset). A variável count permanece privada e não pode ser acessada diretamente.
Closures em Frameworks Modernos de JavaScript
Closures são particularmente importantes em frameworks modernos de JavaScript como o React. No React, closures ajudam a gerenciar estado e efeitos colaterais dentro de componentes funcionais.
import React, { useState } from 'react';
function Counter() {
const [count, setCount] = useState(0);
function increment() {
setCount(prevCount => prevCount + 1);
}
return (
<div>
<p>{count}</p>
<button onClick={increment}>Increment</button>
</div>
);
}Neste exemplo, useState é usado para criar uma variável de estado count e uma função setCount para atualizá-la. A função increment forma uma closure, capturando setCount e count do escopo do componente. Observação: No React, closures podem às vezes capturar estado desatualizado se as dependências não forem gerenciadas adequadamente em hooks como useEffect.
Pratique criar e usar closures em diferentes contextos para compreender plenamente seu potencial e suas limitações. Elas são uma ferramenta essencial no arsenal de qualquer desenvolvedor JavaScript.
Boas Práticas para Usar Closures
- Limite o Escopo: Evite criar closures dentro de loops ou estruturas profundamente aninhadas, a menos que necessário, para prevenir vazamentos de memória e problemas de desempenho.
- Minimize as Variáveis Capturadas: Capture apenas as variáveis que você precisa dentro da closure para reduzir o consumo de memória.
- Evite Capturas Grandes: Não capture objetos grandes ou elementos DOM dentro de closures para evitar retenção desnecessária de memória.
- Use
leteconst: Sempre prefiraletouconstavarpara garantir o escopo adequado e evitar comportamento indesejado. - Faça Limpeza: Sempre que possível, limpe as referências capturadas por closures para evitar vazamentos de memória, especialmente em aplicações de longa duração.
Conclusão
Closures são um recurso poderoso em JavaScript, permitindo privacidade de dados, fábricas de funções e padrões avançados como módulos. Compreender e utilizar closures de forma eficaz pode levar a um código mais robusto e sustentável. Ao dominar closures, você aprimorará sua capacidade de escrever código JavaScript eficiente, seguro e limpo.