Closures em JavaScript
Um closure (fechamento) é uma estrutura na qual uma função criada em um escopo retém seu ambiente léxico, mesmo quando executada fora desse escopo.
Três componentes são necessários para criar um closure:
- Uma função externa que define um escopo e contém algumas variáveis - o ambiente léxico.
- Variáveis (o ambiente léxico) que são definidas na função externa.
- Uma função interna que utiliza essas variáveis.
function outer() {
// Função externa
let n; // Uma variável
return function inner() {
// Função interna
// Ações com a variável n
};
}
Basicamente, a função inner é um closure, relembrando seu ambiente léxico, que inclui a variável n
.
Vamos examinar fechamentos com um exemplo simples:
function outer() {
let x = 5;
function inner() {
x++;
console.log(x);
}
return inner;
}
const fn = outer(); // `fn` se refere à função interna `inner`, pois `outer` a retorna
fn(); // 6
fn(); // 7
fn(); // 8
Aqui, a função outer
define um escopo onde a função interna inner
e a variável x
são definidas. x representa o ambiente léxico para inner
. inner
incrementa x
e o imprime. A função outer
retorna inner
.
const fn = outer();
Como outer retorna inner
, fn
armazena uma referência a inner
, relembrando seu ambiente - a variável x
.
Chamando inner
três vezes, vemos x
sendo incrementada:
fn(); // 6 fn(); // 7 fn(); // 8
Apesar de x
ser definida fora de inner
, inner
se lembra de seu ambiente e pode usá-lo, mesmo sendo chamada fora de outer
.
Cada closure tem seu próprio ambiente:
function outer() {
let x = 5;
function inner() {
x++;
console.log(x);
}
return inner;
}
const fn1 = outer();
const fn2 = outer();
fn1(); // 6
fn1(); // 7
fn2(); // 6
fn2(); // 7
fn1
e fn2
têm suas próprias cópias de x
, que manipulam independentemente.
Outro exemplo:
function multiply(n) {
let x = n;
return function (m) {
return x * m;
};
}
const fn1 = multiply(5);
const result1 = fn1(6); // 30
console.log(result1); // 30
const fn2 = multiply(4);
const result2 = fn2(6); // 24
console.log(result2); // 24
Aqui, multiply()
cria uma função interna que se lembra de x
.
function(m) { return x * m; }
Essa função lembra o ambiente no qual foi criada, em particular, o valor da variável x
.
Portanto, ao chamar a função multiply
, uma constante fn1
é definida, representando um closure que combina duas coisas: a função e o ambiente onde foi criada. O ambiente consiste em quaisquer variáveis locais disponíveis no escopo da função multiply
durante a criação do closure.
Ou seja, fn1
é um closure que contém a função interna function(m) { return x * m; }
e a variável x
existente na criação do closure.
Na criação de dois closures, fn1
e fn2
, cada um tem seu próprio ambiente.
Vale a pena ter cuidado com os parâmetros. Ao definir o closure:
const fn1 = multiply(5);
O número 5 é passado para o parâmetro n
da função multiply
.
Ao chamar a função interna:
const result1 = fn1(6);
O número 6 é passado para o parâmetro m
na função interna function(m) { return x * m; }
.
Também podemos usar outra maneira de chamar o closure:
const result = multiply(5)(6); // 30
console.log(result);
Closures e Programação Orientada a Objetos
Embora a programação orientada a objetos seja tratada posteriormente, é notável que os closures sejam precursores dessa abordagem. De certo modo, o uso de closures permite imitar a criação e manipulação de objetos. Considere o código a seguir:
function person(name, age) {
console.log("Person", name, "created");
function print() {
console.log("Person ", name, " (" + age + ")");
}
function work() {
console.log("Person ", name, " works");
}
function incrementAge(value) {
age += value;
}
return [print, work, incrementAge];
}
const tom = person("Tom", 39);
tom[0](); // print
tom[1](); // work
tom[2](1); // incrementAge
tom[0](); // print
O console do navegador exibe:
Person Tom created Person Tom (39) Person Tom works Person Tom (40)
A função person
recebe dois parâmetros e define três funções aninhadas. Essas funções constituem closures, utilizando os parâmetros da função person
como seu ambiente léxico.
Aqui, person
atua como um construtor para um objeto de uma pessoa, com name
e age
representando seu nome e idade, respectivamente. As três funções aninhadas - print
, work
e incrementAge
- acessam seu ambiente léxico, que consiste nos parâmetros de person
.
Para tornar essas funções acessíveis externamente, person
as retorna como uma matriz:
return [print, work, incrementAge];
Chamando person com parâmetros, obtemos seu resultado :
const tom = person("Tom", 39);
O que é tom
? Essencialmente, é uma matriz de três funções que permitem manipular a pessoa tom
. Podemos chamar uma dessas funções diretamente:
tom[0](); // print
Ou seja, tom[0]
retorna print, e tom[0]()
a chama. O console do navegador exibe:
Person Tom (39)