Como funcionam Closures em JavaScript?

Em JavaScript, uma closure é uma função interna que lembra e tem acesso às variáveis de uma função pai, mesmo após a função pai ter sido executada e suas variáveis terem sido destruídas. Isso é possível porque a closure mantém uma referência às variáveis da função pai em um escopo chamado de ambiente léxico.

Para entender melhor, vamos ver um exemplo:

function outerFunction() {
  var outerVariable = "olá";

  function innerFunction() {
    console.log(outerVariable);
  }

  return innerFunction;
}

var closureExample = outerFunction(); // closureExample agora é uma closure

closureExample(); // imprime 'olá'

Neste exemplo, a função outerFunction cria a variável outerVariable e define uma função interna chamada innerFunction. A innerFunction é retornada como resultado da outerFunction. Quando a outerFunction é chamada e retorna a innerFunction, a variável outerVariable não é destruída e continua acessível pela closure closureExample.

Outro exemplo mais prático poderia ser a utilização de closures para simular variáveis privadas:

var counter = (function () {
  var privateCounter = 0; // variável privada

  function changeBy(val) {
    privateCounter += val;
  }

  return {
    increment: function () {
      changeBy(1);
    },
    decrement: function () {
      changeBy(-1);
    },
    value: function () {
      return privateCounter;
    },
  };
})();

console.log(counter.value()); // imprime 0
counter.increment();
console.log(counter.value()); // imprime 1
counter.decrement();
console.log(counter.value()); // imprime 0

Nesse exemplo, uma IIFE (Immediately Invoked Function Expression) é usada para definir uma função autoexecutável que retorna um objeto contendo métodos. Esses métodos acessam e manipulam a variável privateCounter, que é uma variável privada, através de closures.

É importante lembrar que closures podem causar efeitos colaterais indesejados se usadas de maneira inadequada ou em excesso. Por isso, é recomendado o uso consciente e planejado desse recurso.