Javascript closures: uma explicação simplificada
Aprenda o que são as closures em Javascript e como você pode utilizá-las para deixar o seu código mais modular, organizado e limpo.
O que são Javascript closures?
Uma closure é a combinação de uma função e o ambiente lexical onde essa função foi declarada.
O quê??? Pois é! Essa é mais ou menos a definição que encontramos por ai. Mas não se preocupe, vamos traduzi-la para algo que possamos entender melhor.
Vamos dar uma olhada em um programa com uma chamada de função comum:
function myFunction() {
const text = "Hello from myFunction";
console.log(text);
}myFunction(); // Linha 6
Nesse exemplo, a função myFunction
, quando executada na linha 6, exibe no console o valor da variável text
e então termina o seu ciclo de vida.
A variável text
, que foi declarada dentro do corpo da função myFunction
, fica inacessível para qualquer outra parte do programa e pode ser descartada pelo ambiente de execução.
Mas o que acontece quando utilizamos uma closure? Vejamos um exemplo:
function myFunction() {
let text = "Hello from myFunction";
function changeText(newText) {
text = newText;
} return changeText;
}const setText = myFunction(); // Linha 11setText("Hello again!"); // Linha 13
Nesse exemplo, quando a função myFunction
é executada na linha 11, ela retorna a referência da sua função interna changeText
(que é atribuída à variável setText
) e termina o seu ciclo de vida.
Então a variável text
já pode ser descartada pelo ambiente de execução, certo? Errado!
Ainda que myFunction
tenha terminado seu ciclo de vida, changeText
não terminou o seu, até à linha 13. E durante esse período o escopo interno da função myFunction
é mantido como parte da closure que inclui a função changeText
(atribuída à variável setText
).
A função changeText
e o escopo interno onde foi declarada fazem parte de uma closure, que será descartada apenas quando o ciclo de vida da função changeText
terminar, na linha 13.
E essa é a tradução da nossa definição inicial de closure.
Por que utilizar closures?
As closures são muito úteis porque podemos associar dados a funções que utilizam esses dados. Esse comportamento é mais ou menos o que você obtém quando utiliza objetos com métodos e propriedades.
Mas se eu posso utilizar objetos para obter o mesmo comportamento, por que utilizar as closures?
Entre outras coisas, as closures são mais claras, e seu comportamento é totalmente baseado em funções, o que nos dá um código mais direto, limpo e funcional.
Vamos fazer uma comparação de um mesmo programa utilizando objetos e closures.
Neste exemplo, temos a funcionalidade de “pintar” a cor de um elemento DOM utilizando uma função. Veja:
Utilizando classes e objetos:
class Painter {
constructor(color) {
this.color = color;
} tint(element) {
element.style.color = this.color;
}
}const red = new Painter("red");red.tint(document.querySelector("#myElement"));
Agora, utilizando closures:
function Painter(color) {
return function tint(element) {
element.style.color = color;
}
}const red = Painter("red");red(document.querySelector("#myElement"));
Perceba como o exemplo utilizando closures é mais direto, limpo e totalmente funcional. E além disso, nós não temos que nos preocupar com o estado da variável color
, que é totalmente invariável (imutável).
As closures não descartam a utilização das classes e objetos, mas obviamente elas fornecem um comportamento mais adequado para vários casos de uso que nos deparamos.
Então, sempre quando for definir a arquitetura do seu programa, analise e veja se as closures não são uma alternativa mais adequada ao seu problema.
Um sistema básico de templates utilizando closures
Vamos ver mais um exemplo prático de closures para fixar a sua importância na reutilização de código, modularização e encapsulamento.
Neste exemplo temos um sistema de templates bem simples, onde uma função template
recebe uma string com a estrutura do template e retorna uma outra função (anônima), que será responsável por transformar o template no texto final. Essa função irá simplesmente substituir todas as ocorrências de :name
do template pelo conteúdo da variável str
.
Veja a definição da nossa função template:
function template(myTemplate) {
return function(str) {
return myTemplate.replace(/:name/g, str);
};
}
Agora, como a utilizamos para construir um template hello
:
const hello = template("Hello, my name is :name");// Utilizando a função 'hello' para construir textos:const txt1 = hello("Bruno"); // Hello, my name is Bruno
const txt2 = hello("Karine"); // Hello, my name is Karine
A função hello
, produzida por template
, fica associada à variável myTemplate
(parâmetro da função template
) durante todo o seu ciclo de vida. Elas formam juntas a nossa closure, que poderá ser reutilizada por todo o nosso programa.
Esse é um exemplo bem simples, com apenas uma variável definida para o nosso template, mas que poderia ser transformado facilmente em um sistema mais completo de templates.
Isso mostra como as closures são poderosas na construção de código reutilizável, modular e fácil de entender.
Conclusão
As closures são, sem dúvida, uma das características mais interessantes da linguagem Javascript, e devem ser sempre consideradas uma opção quando formos desenvolver um sistema onde o encapsulamento de dados, modularização e clareza de código forem requisitos.
Assim como as classes e objetos, as closures são um mecanismo poderoso de reutilização de código e podem deixar nossas aplicações mais robustas e funcionais.
Referências
Obrigado
Muito obrigado por ler este texto até o final.
Se você gostou desse conteúdo e gostaria de ver mais artigos como esse, não se esqueça de curtir, e me seguir no twitter. Compartilhe também o conhecimento, envie esse artigo para seus amigos.