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