1 Conceito de Fechamentos
Um fechamento é uma entidade formada por uma função combinada com o ambiente de referência associado. Essa estrutura permite que a função interna acesse variáveis da função externa, mantendo-as em memória mesmo após a conclusão da execução da função exterior. Considere o exemplo a seguir:
function funcaoExterna(a) {
return function funcaoInterna(b) {
return a + b;
};
}
const funcaoFechamento = funcaoExterna(5);
const resultado = funcaoFechamento(3);
console.log(resultado); // Saída: 8
Neste caso, funcaoInterna consegue acessar o parâmetro a passado para funcaoExterna, demonstrando que as variáveis externas são preservadas. As principais características dos fechamentos incluem:
- Acesso e modificação de variáveis capturadas do ambiente externo, permitindo compartilhamento e manutenção de estado.
- Privatização de variáveis, evitando conflitos de nomenclatura e poluição do escopo global.
- Prolongamento do ciclo de vida de variáveis, útil para operações como callbacks e tratamento de eventos.
Essa funcionalidade está intimamente ligada ao conceito de funções de ordem superior, que serão discutidas a seguir.
1.2 Funções de Ordem Superior
Funções de ordem superior são aquelas que aceitam outras funções como parâmetros ou retornam funções como resultado. Métodos como Array.prototype.map, Array.prototype.filter e Array.prototype.reduce são implementações clássicas desse padrão.
1.2.1 Funções que Recebem Funções
function executarVezes(vezes, acao) {
for (let contador = 0; contador < vezes; contador++) {
acao(contador);
}
}
executarVezes(5, function(indice) {
console.log("Olá, " + indice + "!");
});
1.2.2 Funções que Retornam Funções
function somar(valor1) {
return function(valor2) {
return valor1 + valor2;
};
}
const somarCinco = somar(5);
console.log(somarCinco(3)); // Saída: 8
console.log(somarCinco(7)); // Saída: 12
Observa-se que fechamentos se enquadram no segundo caso, constituindo uma forma especial de funções de ordem superior.
2 Aplicações Comuns
2.1 Debouncing
O debouncing garante que uma operação seja executada apenas após um período de inatividade, ideal para eventos frequentes como digitação.
function atrasarExecucao(funcao, tempo) {
let identificador;
return function () {
const contexto = this;
const argumentos = arguments;
clearTimeout(identificador);
identificador = setTimeout(() => {
funcao.apply(contexto, argumentos);
}, tempo);
};
}
2.2 Throttling
Throttling limita a frequência de execução de uma função em intervalos regulares, útil para controlar a taxa de eventos.
function limitarFrequencia(funcao, intervalo) {
let identificador;
return function () {
const contexto = this;
const argumentos = arguments;
if (!identificador) {
identificador = setTimeout(function () {
funcao.apply(contexto, argumentos);
identificador = null;
}, intervalo);
}
};
}
2.3 Função Curry
Currying transforma uma função com múltiplos parâmetros em uma sequência de funções com um único argumento.
function somar() {
const valores = [];
function curried(...numeros) {
if (numeros.length === 0) {
return valores.reduce((acumulador, atual) => acumulador + atual, 0);
} else {
valores.push(...numeros);
return curried;
}
}
return curried(...Array.from(arguments));
}
console.log(somar(1, 2)(1)()); // Saída: 4
console.log(somar(1)(2)(3)(4)()); // Saída: 10
2.4 Iteradores
Iteradores permitem percorrer conjuntos de dados de forma controlada. Implementação com fechamento para percorrer um intervalo numérico:
function iteradorIntervalo(inicio, fim) {
let atual = inicio;
return {
proximo: function() {
if (atual <= fim) {
return { valor: atual++, concluido: false };
} else {
return { concluido: true };
}
}
};
}
const iterador = iteradorIntervalo(1, 5);
console.log(iterador.proximo()); // { valor: 1, concluido: false }
console.log(iterador.proximo()); // { valor: 2, concluido: false }
2.5 Encadeamento de Chamadas
Encadeamento permite realizar múltiplas operações em sequência, retornando o próprio objeto após cada método. Exemplo com abordagem funcional:
function encadeavel(valor) {
let resultado = valor;
const quadrado = function () {
resultado = Math.pow(resultado, 2);
return this;
};
const cubo = function () {
resultado = Math.pow(resultado, 3);
return this;
};
const negar = function () {
resultado = -resultado;
return this;
};
const aleatorio = function () {
resultado = Math.random() * resultado;
return this;
};
const obterValor = function () {
return resultado;
};
return {
quadrado,
cubo,
negar,
aleatorio,
obterValor
}
}
const valorFinal = encadeavel(5).quadrado().cubo().negar().aleatorio().obterValor();
console.log(valorFinal);
2.6 Padrão Publciar-Subscrever
Este padrão estabelece comunicação desacoplada entre componentes. Implementação usando fechamentos:
function emissorEventos() {
const eventos = {};
function inscrever(nomeEvento, funcaoCallback) {
eventos[nomeEvento] = eventos[nomeEvento] || [];
eventos[nomeEvento].push(funcaoCallback);
}
function emitir(nomeEvento, ...argumentos) {
const callbacks = eventos[nomeEvento];
if (callbacks) {
callbacks.forEach(callback => callback(...argumentos));
}
}
function desinscrever(nomeEvento, funcaoCallback) {
if (!funcaoCallback) {
delete eventos[nomeEvento];
} else {
eventos[nomeEvento] = eventos[nomeEvento].filter(cb => cb !== funcaoCallback);
}
}
return { inscrever, emitir, desinscrever };
}
const emissor = emissorEventos();
function manipulador1(nome) {
console.log(nome + " enviou saudação do manipulador1");
}
emissor.inscrever('saudacao', manipulador1);
emissor.emitir('saudacao', 'Alice');
2.7 Memoização
Memoização armazena resultados de funções para evitar cálculos redundantes, demonstrando o uso de fechamentos para cache.
function memorizar(funcao) {
const cache = {};
return function (...argumentos) {
const inicio = performance.now();
const chave = JSON.stringify(argumentos);
if (cache[chave] === undefined) {
cache[chave] = funcao.apply(this, argumentos);
}
const tempoDecorrido = performance.now() - inicio;
console.log('Tempo decorrido:', tempoDecorrido);
return cache[chave];
};
}
const obterTempo = memorizar((vezes) => {
let contador = 0;
for (let i = 0; i < vezes; i++) {
contador++;
}
return vezes;
});
obterTempo(10000000);
obterTempo(10000000); // Resultado em cache, tempo próximo a zero
3 Desvantagens dos Fechamentos
3.1 Vazamentos de Memória
Fechamenots podem causar vazamentos de memória quando mantêm referências a variáveis desnecessárias. Exemplo problemático:
function externa() {
let contador = 0;
function interna() {
contador++;
console.log(contador);
}
return interna;
}
const funcao = externa();
funcao(); // Chamadas múltiplas mantêm 'contador' em memória indevidamente
Solução: Atribuir null à referência do fechamento ou reatribuir a função quando não for mais necessária.
3.2 Desempenho na Busca de Escopo
A resolução de variáveis via cadeia de escopo pode impactar o desempenho em operações intensivas. Para mitigar:
- Limitar o uso de fechamentos a cenários essenciais.
- Reduzir acessos a variáveis externas sempre que possível.
- Implementar caches para resultados de fechamentos frequentemente chamados.
3.3 Riscos de Segurança e Vazamento de Informações
Fechamentos podem expor dados privados se usados incorretamente. Para proteção:
- Armazenar informações sensíveis fora do fechamento ou utilizar técnicas de controle de acesso.
- Encapsular fechamentos em funções de invocação imediata, retornando apenas interfaces públicas necessárias.