Atalhos de JavaScript para iniciantes

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(); // '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());  // 0 console.log(count());  // 1 console.log(count());  // 2 

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ção

Quando 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 chamadas

Quando 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(); // '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());  // 0 console.log(count());  // 1 console.log(count());  // 2 

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.

Source: https://habr.com/ru/post/pt424967/


All Articles