Quando você usa um objeto, variável ou função, você o faz de propósito. Você pensa: "É aqui que preciso de uma variável" e a adiciona ao seu código. Os fechamentos, entretanto, são outra coisa. Embora a maioria dos programadores esteja começando a aprender sobre encerramentos, essas pessoas já estão usando encerramentos sem saber. Provavelmente a mesma coisa acontece com você. Portanto, aprender a encerrar significa menos aprender uma nova ideia do que aprender a reconhecer algo que você já encontrou muitas vezes. Resumindo, o fechamento é quando uma função se refere a variáveis declaradas fora dela. Por exemplo, o encerramento está contido neste trecho de código:
let users = ['Alice', 'Dan', 'Jessica'];
let query = 'A';
let user = users.filter(user => user.startsWith(query));
Observe que
user => user.startsWith(query)é uma função. Ela usa uma variável query. E essa variável é declarada fora da função. Este é um encerramento.
Você pode pular a leitura se quiser. O resto deste material olha para os fechos sob uma luz diferente. Em vez de falar sobre o que são fechamentos, esta parte do artigo entrará em detalhes sobre a detecção de fechamentos. Isso é semelhante a como os primeiros programadores trabalharam na década de 1960.
Etapa 1: as funções podem acessar variáveis declaradas fora delas
Para entender os encerramentos, você precisa estar bastante familiarizado com as variáveis e funções. Neste exemplo, estamos declarando uma variável
fooddentro de uma função eat:
function eat() {
let food = 'cheese';
console.log(food + ' is good');
}
eat(); // 'cheese is good'
E se você quiser alterar o valor de uma variável posteriormente
food, fora da função eat? Para fazer isso, podemos remover a própria variável da função foode movê-la para um nível superior:
let food = 'cheese'; //
function eat() {
console.log(food + ' is good');
}
Isso permite que você altere a variável
food"de fora" quando necessário:
eat(); // 'cheese is good'
food = 'pizza';
eat(); // 'pizza is good'
food = 'sushi';
eat(); // 'sushi is good'
Em outras palavras, a variável
foodnão é mais eatlocal para a função . Mas a função eat, apesar disso, não tem problemas ao trabalhar com esta variável. As funções podem acessar variáveis declaradas fora delas. Pare um pouco e verifique você mesmo, certifique-se de que não tenha problemas com essa ideia. Uma vez que esse pensamento esteja firmemente estabelecido em sua mente, passe para a segunda etapa.
Etapa 2: colocar o código na chamada de função
Digamos que temos algum código:
/* */
Não importa qual código seja. Mas digamos que precisamos executá-lo duas vezes.
A primeira maneira de fazer isso é apenas fazer uma cópia do código:
/* */
/* */
Outra maneira é colocar o código em um loop:
for (let i = 0; i < 2; i++) {
/* */
}
E a terceira maneira, que é especialmente interessante para nós hoje, é colocar esse código em uma função:
function doTheThing() {
/* */
}
doTheThing();
doTheThing();
O uso de uma função nos dá o máximo de flexibilidade, pois nos permite chamar um determinado código qualquer número de vezes, a qualquer momento e de qualquer lugar do programa.
Na verdade, se necessário, podemos nos limitar a apenas uma única chamada da nova função:
function doTheThing() {
/* */
}
doTheThing();
Observe que o código acima é equivalente ao snippet de código original:
/* */
Em outras palavras, se pegarmos algum pedaço de código e o "envolvermos" em uma função, e então chamarmos essa função exatamente uma vez, não iremos influenciar de forma alguma o que esse código faz. Existem algumas exceções a esta regra, às quais não iremos prestar atenção, mas, em geral, podemos assumir que esta regra é verdadeira. Pense um pouco, acostume-se com essa ideia.
Etapa 3: detectar fechamentos
Descobrimos duas ideias:
- As funções podem funcionar com variáveis declaradas fora delas.
- Se você colocar o código em uma função e chamar essa função uma vez, isso não afetará os resultados do código.
Agora vamos falar sobre o que acontecerá se essas duas ideias forem combinadas.
Vamos pegar o código de amostra que vimos na primeira etapa:
let food = 'cheese';
function eat() {
console.log(food + ' is good');
}
eat();
Agora vamos colocar todo este exemplo em uma função que planejamos chamar apenas uma vez:
function liveADay() {
let food = 'cheese';
function eat() {
console.log(food + ' is good');
}
eat();
}
liveADay();
Leia os dois exemplos de código anteriores e verifique se eles são equivalentes.
O segundo exemplo funciona! Mas vamos dar uma olhada nisso. Observe que a função
eatestá dentro de uma função liveADay. Isso é permitido em JavaScript? É realmente possível envolver uma função dentro de outra?
Existem linguagens nas quais o código estruturado desta forma estará incorreto. Por exemplo, em C, esse código estaria errado (não há fechamentos nesta linguagem). Isso significa que, ao usar C, nossa segunda conclusão estará errada - você não pode simplesmente pegar uma parte arbitrária de código e "envolvê-la" em uma função. Mas não existe essa limitação no JavaScript.
Vamos pensar sobre esse código novamente, prestando atenção especial a onde a variável é declarada e onde é usada.
food:
function liveADay() {
let food = 'cheese'; // `food`
function eat() {
console.log(food + ' is good'); // `food`
}
eat();
}
liveADay();
Vamos percorrer este código passo a passo juntos. Primeiro, declaramos, no nível superior, uma função
liveADay. Ligamos para ela imediatamente. Esta função possui uma variável local food. A função também é declarada nele eat. Em seguida, a liveADayfunção é chamada internamente eat. Como a função eatestá dentro de uma função liveADay, ela eat"vê" todas as variáveis declaradas em liveADay. É por isso que a função eatpode ler o valor da variável food.
Isso é chamado de fechamento.
Falamos sobre a existência de um fechamento quando uma função (como
eat) lê ou grava o valor de uma variável (como food) que é declarada fora dela (por exemplo, em uma função liveADay).
Pense nessas palavras, releia-as. Teste a si mesmo descobrindo do que estamos falando em nosso código de amostra.
Aqui está um exemplo que foi dado no início do artigo:
let users = ['Alice', 'Dan', 'Jessica'];
let query = 'A';
let user = users.filter(user => user.startsWith(query));
Pode ser mais fácil notar o fechamento reescrevendo este exemplo usando uma expressão de função:
let users = ['Alice', 'Dan', 'Jessica'];
// 1. query
let query = 'A';
let user = users.filter(function(user) {
// 2.
// 3. query ( !)
return user.startsWith(query);
});
Quando uma função acessa uma variável declarada fora dela, a chamamos de encerramento. O termo em si é usado de forma bastante livre. Algumas pessoas chamarão a própria função aninhada, mostrada no exemplo, "closure". Outros podem se referir a um acessador de variável externa chamando-o de "encerramento". Na prática, isso não importa.
Função chamada fantasma
Encerramentos podem parecer um conceito enganosamente simples para você agora. Mas isso não significa que eles não tenham alguns recursos não óbvios. Se você pensar cuidadosamente sobre o fato de que uma função pode ler e gravar valores de variáveis fora dela, descobrirá que isso tem consequências bastante sérias.
Por exemplo, isso significa que tais variáveis “viverão” enquanto uma função aninhada em outra função puder ser chamada.
function liveADay() {
let food = 'cheese';
function eat() {
console.log(food + ' is good');
}
// eat
setTimeout(eat, 5000);
}
liveADay();
Neste exemplo,
foodé uma variável local dentro de uma chamada de função liveADay(). Só quero decidir que essa variável "desaparecerá" após sair da função e não voltará para nos assombrar como um fantasma.
Mas na função,
liveADaypedimos ao navegador para chamar a função eatapós cinco segundos. E esta função lê o valor da variável food. Como resultado, o mecanismo JavaScript precisa manter a variável foodassociada a essa chamada ativa liveADay()até que a função seja chamada eat.
Nesse sentido, os encerramentos podem ser pensados como “fantasmas” de chamadas de função anteriores ou como “memórias” de tais chamadas. Mesmo que a execução da função
liveADay()terminou há muito tempo, as variáveis declaradas nele devem continuar a existir enquanto a função aninhada eatpuder ser chamada. Felizmente, o JavaScript cuida desses mecanismos, então não precisamos fazer nada de especial nessas situações.
Por que os "fechamentos" são chamados dessa forma?
Você pode estar se perguntando por que os "fechamentos" são chamados dessa forma. A razão para isso é principalmente histórica. Qualquer pessoa familiarizada com o jargão da informática pode dizer que uma expressão como essa
user => user.startsWith(query)tem uma "ligação aberta". Em outras palavras, fica claro a partir dessa expressão o que user(parâmetro) é, mas quando visto isoladamente, não fica claro o que é query. Quando dizemos que ela é, de fato, queryuma variável declarada fora da função, estamos "fechando" essa ligação aberta. Em outras palavras, obtemos um encerramento.
Os fechamentos não são implementados em todas as linguagens de programação. Por exemplo, em algumas linguagens, como C, você não pode usar funções aninhadas. Como resultado, a função pode trabalhar apenas com suas variáveis locais ou com variáveis globais. No entanto, nunca há uma situação em que ele possa acessar as variáveis locais da função pai. Esta é realmente uma limitação muito desagradável.
Existem também linguagens como Rust que implementam fechamentos. Mas eles usam sintaxe diferente para descrever fechamentos e funções normais. Como resultado, se você precisa ler o valor de uma variável fora de uma função, então, usando Rust, você precisa usar uma construção especial. A razão para isso é que o uso de encerramentos pode exigir que os mecanismos internos da linguagem mantenham variáveis externas (chamadas de "ambiente") mesmo após a conclusão da chamada de função. Essa carga adicional no sistema é aceitável em JavaScript, mas pode, quando usada em linguagens de baixo nível, causar problemas de desempenho.
Agora, espero que você entenda o conceito de encerramentos em JavaScript.
Você está tendo dificuldade em entender os conceitos de JavaScript?
