O fechamento é um dos conceitos fundamentais do JavaScript, causando dificuldades para muitos iniciantes, que todo programador de JS deve conhecer e entender. Para entender bem os fechamentos, é possível escrever um código melhor, mais eficiente e mais limpo. E isso, por sua vez, contribuirá para o seu crescimento profissional.
O material, cuja tradução publicamos hoje, é dedicado a uma história sobre os mecanismos internos de fechamento e como eles funcionam nos programas JavaScript.
O que é um fechamento?
Um fechamento é uma função que tem acesso a um escopo formado por uma função externa em relação a ele, mesmo após a conclusão dessa função externa. Isso significa que um fechamento pode armazenar variáveis declaradas em uma função externa e argumentos passados para ela. Antes de prosseguirmos, de fato, para os fechamentos, trataremos do conceito de "ambiente lexical".
O que é um ambiente lexical?
O termo "ambiente lexical" ou "ambiente estático" em JavaScript refere-se à capacidade de acessar variáveis, funções e objetos com base em sua localização física no código-fonte. Considere um exemplo:
let a = 'global';  function outer() {    let b = 'outer';    function inner() {      let c = 'inner'      console.log(c);   // 'inner'      console.log(b);   // 'outer'      console.log(a);   // 'global'    }    console.log(a);     // 'global'    console.log(b);     // 'outer'    inner();  } outer(); console.log(a);         // 'global' 
Aqui, a função 
inner() tem acesso a variáveis declaradas em seu próprio escopo, no escopo da função 
outer() e no escopo global. A função 
outer() tem acesso a variáveis declaradas em seu próprio escopo e no escopo global.
A cadeia de escopo do código acima ficará assim:
 Global { outer {   inner } } 
Observe que a função 
inner() é cercada pelo ambiente lexical da função 
outer() , que por sua vez é cercada por um escopo global. É por isso que a função 
inner() pode acessar variáveis declaradas na função 
outer() e no escopo global.
Exemplos práticos de fechamentos
Considere, antes de desmontar os meandros do circuito interno, alguns exemplos práticos.
▍ Exemplo No. 1
 function person() { let name = 'Peter'; return function displayName() {   console.log(name); }; } let peter = person(); peter();  
Aqui chamamos a função 
person() , que retorna a função interna 
displayName() , e armazena essa função na variável 
peter . Quando, depois disso, chamamos a função 
peter() (a variável correspondente realmente armazena uma referência à função 
displayName() ), o nome 
Peter é exibido no console.
Ao mesmo tempo, a função 
displayName() não possui uma variável chamada 
name , portanto, podemos concluir que essa função pode acessar a variável declarada na função externa a ela, 
person() , mesmo depois disso. como essa função funcionou. Talvez seja porque a função 
displayName() é realmente um fechamento.
▍ Exemplo No. 2
 function getCounter() { let counter = 0; return function() {   return counter++; } } let count = getCounter(); console.log(count());   
Aqui, como no exemplo anterior, armazenamos o link para a função interna anônima retornada pela função 
getCounter() na 
count variáveis. Como a função 
count() é um fechamento, ela pode acessar a variável de 
counter da função 
getCount() mesmo depois que a função 
getCounter() concluiu seu trabalho.
Observe que o valor da variável do 
counter não é redefinido para 0 cada vez que a função 
count() é chamada. Pode parecer que seja redefinido para 0, como seria ao chamar uma função regular, mas isso não acontece.
Isso funciona assim porque toda vez que a função 
count() é chamada, um novo escopo é criado para ela, mas existe apenas um escopo para a função 
getCounter() . Como a variável do 
counter é declarada no escopo da função 
getCounter() , seu valor entre as chamadas para a função 
count() é salvo sem redefinir para 0.
Como funcionam os curtos-circuitos?
Até agora, falamos sobre o que são fechamentos e examinamos exemplos práticos. Agora vamos falar sobre os mecanismos internos do JavaScript que os fazem funcionar.
Para entender os fechamentos, precisamos lidar com dois conceitos cruciais do JavaScript. Este é o contexto de execução e o ambiente lexical.
Context Contexto de execução
O contexto de execução é um ambiente abstrato no qual o código JavaScript é calculado e executado. Quando o código global é executado, isso acontece dentro do contexto de execução global. O código da função é executado dentro do contexto da execução da função.
Em algum momento, o código pode ser executado em apenas um contexto de execução (JavaScript é uma linguagem de programação de thread único). Esses processos são gerenciados usando a chamada pilha de chamadas.
A pilha de chamadas é uma estrutura de dados organizada de acordo com o princípio LIFO (Last In, First Out - Last In, First Out). Novos elementos podem ser colocados apenas no topo da pilha e apenas elementos podem ser removidos.
O contexto de execução atual sempre estará no topo da pilha e, quando a função atual sair, seu contexto de execução será extraído da pilha e o controle será transferido para o contexto de execução, localizado abaixo do contexto dessa função na pilha de chamadas.
Considere o exemplo a seguir para entender melhor o que são o contexto de execução e a pilha de chamadas:
Exemplo de contexto de execuçãoQuando esse código é executado, o mecanismo JavaScript cria um contexto de execução global para a execução do código global e, quando encontra uma chamada para a 
first() função 
first() , cria um novo contexto de execução para essa função e o coloca no topo da pilha.
A pilha de chamadas desse código é assim:
Pilha de chamadasQuando a execução da 
first() função 
first() é concluída, seu contexto de execução é recuperado da pilha de chamadas e o controle é transferido para o contexto de execução abaixo, ou seja, para o contexto global. Depois disso, o código restante no escopo global será executado.
Environment ambiente lexical
Cada vez que o mecanismo JS cria um contexto de execução para executar uma função ou código global, também cria um novo ambiente lexical para armazenar as variáveis declaradas nessa função durante sua execução.
O ambiente lexical é uma estrutura de dados que armazena informações sobre a correspondência de identificadores e variáveis. Aqui, "identificador" é o nome de uma variável ou função e "variável" é uma referência a um objeto (isso inclui funções) ou um valor de um tipo primitivo.
O ambiente lexical contém dois componentes:
- Um registro de ambiente é o local em que as declarações de variáveis e funções são armazenadas.
- Referência ao ambiente externo - um link que permite acessar o ambiente lexical externo (principal). Esse é o componente mais importante que precisa ser tratado para entender os fechamentos.
Conceitualmente, o ambiente lexical é assim:
 lexicalEnvironment = { environmentRecord: {   <identifier> : <value>,   <identifier> : <value> } outer: < Reference to the parent lexical environment> } 
Dê uma olhada no seguinte trecho de código:
 let a = 'Hello World!'; function first() { let b = 25;  console.log('Inside first function'); } first(); console.log('Inside global execution context'); 
Quando o mecanismo JS cria um contexto de execução global para a execução de código global, ele também cria um novo ambiente lexical para armazenar variáveis e funções declaradas no escopo global. Como resultado, o ambiente lexical do escopo global ficará assim:
 globalLexicalEnvironment = { environmentRecord: {     a : 'Hello World!',     first : < reference to function object > } outer: null } 
Observe que a referência ao ambiente lexical externo ( 
outer ) está definida como 
null , pois o escopo global não possui um ambiente lexical externo.
Quando o mecanismo cria um contexto de execução para a 
first() função 
first() , ele também cria um ambiente lexical para armazenar as variáveis declaradas nessa função durante sua execução. Como resultado, o ambiente lexical da função ficará assim:
 functionLexicalEnvironment = { environmentRecord: {     b : 25, } outer: <globalLexicalEnvironment> } 
O link para o ambiente lexical externo da função é definido como 
<globalLexicalEnvironment> , pois no código-fonte o código da função está no escopo global.
Observe que, quando a função termina seu trabalho, seu contexto de execução é recuperado da pilha de chamadas, mas seu ambiente lexical pode ser excluído da memória ou pode permanecer lá. Depende se em outros ambientes lexicais existem referências a esse ambiente lexical na forma de links para um ambiente lexical externo.
Análise detalhada de exemplos de trabalho com fechamentos
Agora que nos preparamos para o conhecimento do contexto de execução e do ambiente lexical, retornaremos ao encerramento e analisaremos com mais profundidade os mesmos fragmentos de código que já examinamos.
▍ Exemplo No. 1
Dê uma olhada neste snippet de código:
 function person() { let name = 'Peter'; return function displayName() {   console.log(name); }; } let peter = person(); peter();  
Quando a função 
person() é executada, o mecanismo JS cria um novo contexto de execução e um novo ambiente lexical para essa função. Terminando o trabalho, a função retorna a função 
displayName() , uma referência a esta função é gravada na variável 
peter .
Seu ambiente lexical ficará assim:
 personLexicalEnvironment = { environmentRecord: {   name : 'Peter',   displayName: < displayName function reference> } outer: <globalLexicalEnvironment> } 
Quando a função 
person() sai, seu contexto de execução é exibido na pilha. Mas seu ambiente lexical permanece na memória, pois existe um link para ele no ambiente lexical de sua função interna 
displayName() . Como resultado, as variáveis declaradas nesse ambiente lexical permanecem disponíveis.
Quando a função 
peter() é chamada (a variável correspondente armazena uma referência à função 
displayName() ), o mecanismo JS cria um novo contexto de execução e um novo ambiente lexical para esta função. Esse ambiente lexical terá a seguinte aparência:
 displayNameLexicalEnvironment = { environmentRecord: {   } outer: <personLexicalEnvironment> } 
Não há variáveis na função 
displayName() , portanto, seu registro de ambiente estará vazio. Durante a execução desta função, o mecanismo JS tentará encontrar a variável 
name no ambiente lexical da função.
Como a pesquisa não pode ser encontrada no ambiente lexical da função 
displayName() , a pesquisa continuará no ambiente lexical externo, ou seja, no ambiente lexical da função 
person() , que ainda está na memória. Lá, o mecanismo encontra a variável desejada e exibe seu valor no console.
▍ Exemplo No. 2
 function getCounter() { let counter = 0; return function() {   return counter++; } } let count = getCounter(); console.log(count());   
O ambiente lexical da função 
getCounter() terá a seguinte aparência:
 getCounterLexicalEnvironment = { environmentRecord: {   counter: 0,   <anonymous function> : < reference to function> } outer: <globalLexicalEnvironment> } 
Essa função retorna uma função anônima atribuída à variável 
count .
Quando a função 
count() é executada, seu ambiente lexical fica assim:
 countLexicalEnvironment = { environmentRecord: { } outer: <getCountLexicalEnvironment> } 
Ao executar esta função, o sistema procurará a variável do 
counter em seu ambiente lexical. Nesse caso, novamente, o registro do ambiente da função está vazio; portanto, a pesquisa pela variável continua no ambiente lexical externo da função.
O mecanismo localiza a variável, a exibe no console e incrementa a variável do 
counter , que é armazenada no ambiente lexical da função 
getCounter() .
Como resultado, o ambiente lexical da função 
getCounter() após a primeira chamada para a função 
count() terá a seguinte aparência:
 getCounterLexicalEnvironment = { environmentRecord: {   counter: 1,   <anonymous function> : < reference to function> } outer: <globalLexicalEnvironment> } 
Cada vez que a função 
count() é chamada, o mecanismo JavaScript cria um novo ambiente lexical para essa função e incrementa a variável do 
counter , o que leva a alterações no ambiente lexical da função 
getCounter() .
Sumário
Neste artigo, falamos sobre o que são fechamentos e resolvemos os mecanismos JavaScript subjacentes a eles. Os fechamentos são um dos conceitos fundamentais mais importantes sobre JavaScript, e todo desenvolvedor de JS deve entendê-los. Entender os fechamentos é uma das etapas para criar aplicativos eficazes e de alta qualidade.
Caros leitores! Se você tem experiência no desenvolvimento de JS, compartilhe exemplos práticos de uso de fechamentos com iniciantes.
