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.

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.

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.

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.

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.

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.

Software engineer.

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store