Closures em JavaScript

Segundo o site Mozila uma closure é:

É a combinação de uma função com as referências ao estado que a circunda (o ambiente léxico). Em outras palavras, uma closure lhe dá acesso ao escopo de uma função externa a partir de uma função interna. Em JavaScript, as closures são criadas toda vez que uma função é criada, no momento da criação da função.

De uma forma um pouco mais simples, temos um closures, quando temos uma função dentro de outra, essa segunda função mais interna é a nossa closure, sei que é um pouco difícil de visualizar, mas vamos para um exemplo simples:

let firstName = 'Maycon'
function displayName() {
  let lastName = 'Alves'; // LastName é uma variável local criada dentro da função displayName
  function joinNames() {
    // joinNames() é a função interna, uma closure
    // usa a variável declarada na função pai,
    // mas também pode usar a várial global
    console.log(`${firstName} ${lastName}`);
  }
  joinNames();
}
displayName();

Então esse é o exemplo mais simples de closure. Dessa forma, no exemplo acima a função joinNames só está visível dentro da função, displayName isso torna nossa função "privada".

Pode parecer não muito intuitivo de que o código de fato funciona. Normalmente variáveis locais de uma função, apenas existem pela duração de sua execução. Uma vez que displayName() terminou de executar, é razoável esperar que a variável lastName não será mais necessária.

Vamos para mais um exemplo:

const add = (x) => {
  let secondNumber = 2
  function makeAdder() {
    return console.log(x + secondNumber)
  }
  makeAdder();
}

console.log(add(2))
console.log(add(3))

Nesse exemplo, tenho a função add que faz contas de soma, porém você não tem acesso à função que de fato faz essa soma, se você fizer algo dessa forma: console.log(add.makeAdder()) você irá receber um erro add.maker() is not a function.

Podemos criar funções privadas com closure, os métodos privados só podem ser chamados dentro da própria classe (contexto), por isso eles podem deixar seu código mais seguro, como também ajuda que funções globais não tenham o mesmo nome.

const makeLogin = () => {
  let privateUser = "Maycon";
  const useLogin = (val) => {
    if (val === privateUser) {
      return console.log("Logado com sucesso")
    } else {
      return console.log("Usuário ou senha incorretos")
    };
  }
  return {
    login: (user) => {
      useLogin(user);
    },
  };
};

const tryAcess = makeLogin()
console.log(tryAcess.login("Maycon"))
// deve retornar logado com sucesso

// se eu mudar a linha do console.log e chamar só a tryAcess
// conseguimos ver o que tem dentro de makeLogin


console.log(tryAcess)
// resultado { login: [Function: login] }

No exemplo acima, criei um login fake (bem raso) só para entender como uma closure se comporta, um usuário de fora só consegue visualizar e chamar a função login⁣ , mas não tem acesso à função useLogin que é onde fazemos a lógica de deixar o cara logar ou não, então podemos fazer coisas bem legais com closures.

Tentei ser o mais simples possível para poder passar o conceito de closures, espero que tenham gostado, qualquer dúvida ou feedback é muito bem-vindo, é isso aí, abraços quentinhos para vocês.

Uma das utilidades de closure é o caso clássico de precisar usar uma função anônima de callback dentro de um loop. Exemplo:

for (var i = 0; i < 5; i++) {
    setTimeout(function () { console.log(i) }, 1000 * i);
}

Ou seja, em cada iteração do for, eu chamo setTimeout para imprimir o valor de i depois de algum tempo.

Só que este código não funciona da maneira esperada, porque na verdade ele imprime cinco vezes o número 5 (que é o valor que i tem ao final do loop).

Uma solução é criar um novo contexto léxico que "capture" o valor da variável no momento desejado:

for (var i = 0; i < 5; i++) {
    (function(valor) {
            setTimeout(function () { console.log(valor) }, 1000 * valor);
    })(i);
}

Ou seja, eu defino uma função que recebe um valor, e este é o valor que eu passo para o callback de setTimeout. Ao mesmo tempo, eu já chamo esta função passando o valor de i. Esta sintaxe - que eu particularmente acho meio confusa - é chamada de IIFE (Immediately Invoked Function Expression): basicamente, consiste em definir uma função e chamá-la imediatamente, tudo de uma vez.

Desta forma, agora ele imprime corretamente os números 0, 1, 2, 3 e 4.


Mas claro que esta não é a única forma de definir closures, vc também pode usar uma função "normal". Por exemplo, se eu quiser percorrer um array de forma circular, uma solução seria:

function makeCircular(arr) {
    var current = -1;
    // retorna uma função que incrementa "current" e retorna o respectivo elemento do array
    return function () {
        current = (current + 1) % arr.length;
        return arr[current];
    }
}

const a = ['A', 'B', 'C'];
let circular = makeCircular(a);

// imprime A, B, C, A, B, C...
for(i = 0; i < 20; i++) {
    console.log(circular());
}

Eu até poderia fazer a variável current ser global, mas aí qualquer um poderia alterá-la para valores arbitrários e isso poderia quebrar a "circularidade". Usando um closure, eu garanto que current só pode ser alterado dentro da função.

Inclusive, isso permite que eu crie vários closures independentes, pois cada um vai ter seu próprio current:

function makeCircular(arr) {
    var current = -1;
    return function () {
        current = (current + 1) % arr.length;
        return arr[current];
    }
}

const a = ['A', 'B', 'C'];
let circ1 = makeCircular(a);
let circ2 = makeCircular(a);
let circ3 = makeCircular(a);

console.log(circ1()); // A
console.log(circ1()); // B
console.log(circ2()); // A
console.log(circ2()); // B
console.log(circ1()); // C
console.log(circ3()); // A
mano um ótimo exemplo, é complexo usar closure dentro do for, por que se você errar entramos no loop infinito, mas foi uma ótima demonstração de como podemos usar dentro de um loop.

Ótima explicação, closures realmente são muito poderosas e realmente é um ponto chave que torna o javascript tão poderoso e funcional, porém nem tudo são flores.

Tem sempre que lembrar que o GC não vai liberar a memória daquelas váriaveis que estão sendo usadas na funciona mais interna através de closures, logo, tem que tomar muito cuidado pra não alocar uma váriavel maior que devia e ficar com aquilo alocado na memória durante todo o ciclo de vida da função.

Recomendo esse artigo do meteor blog que mostra como identificaram um memory leak por causa de um mal uso de closures https://blog.meteor.com/an-interesting-kind-of-javascript-memory-leak-8b47d2e7f156

Sim com certeza, precisamos ter um pouco de cuidado com memory leak no JS justamente por não termos um código compilado, muito obrigado pelo feedback e complementar mais ainda o meu artigo, vou ler o que você me mandou também, muito obrigado de novo.