Ambiente Lexical e fechamentos em EcmaScript

Olá Habr!

Não escrevo nada há muito tempo, muito trabalho no projeto nas últimas semanas, mas agora como tenho tempo livre, decidi apresentar-lhe um novo artigo.

Hoje, continuaremos analisando os principais conceitos do EcmaScript, conversando sobre o ambiente Lexical e o Closure. Compreender o conceito do ambiente Lexical é muito importante para entender o fechamento, e o fechamento é a base de tantas boas técnicas e tecnologias no mundo JS (que é baseado na especificação EcmaScript).

Então, vamos começar.

Ambiente lexical (LexicalEnvironment, LO, LE)


A especificação oficial do ES6 define esse termo como:
Ambiente Lexical é um tipo de especificação usada para resolver nomes de identificadores ao procurar variáveis ​​e funções específicas com base na estrutura lexical do aninhamento de código ECMAScript. O ambiente Lexical consiste em um registro do ambiente e, possivelmente, uma referência nula ao ambiente Lexical externo.
Vamos dar uma olhada.

Imaginarei o ambiente lexical como um tipo de estrutura que armazena a conexão dos identificadores de contexto com seu significado. Este é um tipo de repositório de variáveis, funções, classes declaradas no escopo deste contexto.

Tecnicamente, LO é um objeto com duas propriedades:

  • registro de ambiente (é onde todos os anúncios são armazenados)
  • link para o contexto generativo do LO.

Por meio de um link para o contexto pai do contexto atual, podemos, se necessário, obter um link para o "contexto avô" do contexto pai e assim por diante, para o contexto global, cuja referência ao pai será nula. A partir dessa definição, segue-se que o ambiente lexical é a conexão da entidade com os contextos de sua origem. Um tipo de ScopeChain em funções é um análogo do ambiente Lexical. Falamos sobre o ScopeChain em detalhes neste artigo .

let x = 10; let y = 20; const foo = z => { let x = 100; return x + y + z; } foo(30);// 150.   foo    {record: {z: 30, x: 100}, parent: __parent}; // __parent      {record: {x: 10, y: 20}, parent: null} 

Tecnicamente, o processo de resolução de nomes de identificadores ocorrerá como no ScopeChain, ou seja, a pesquisa seqüencial de objetos no loop LO ocorrerá até que o identificador desejado seja encontrado. Se o identificador não for encontrado, o ReferenceError.

O ambiente lexical é criado e preenchido no estágio de criação do contexto. Quando o contexto atual termina a execução, ele é removido da pilha de chamadas, mas seu ambiente Lexical pode continuar ativo enquanto houver pelo menos um link para ele. Essa é uma das vantagens de uma abordagem moderna para o design de linguagens de programação. Eu acho que vale a pena falar!

Organização da pilha versus memória compartilhada dinamicamente


Nos idiomas da pilha, as variáveis ​​locais são armazenadas na pilha, que são reabastecidas quando a função é ativada; quando a função sai, suas variáveis ​​locais são removidas da pilha.

Com uma organização empilhada, não seria possível retornar uma função local de uma função ou chamar uma função para uma variável livre.

Uma variável livre é uma variável usada por uma função, mas não é um parâmetro formal nem uma variável local para essa função.

 function testFn() { var locaVar = 10; //     innerFn function innerFn(p) { alert(p + localVar); } return innerFn; //  } var test = testFn();//  innerFn   test();//       

Com a organização da pilha, nem a pesquisa locaVar no LexicalEnvironment externo nem o retorno da função innerFn seriam possíveis, pois innerFn também é uma declaração local para testFn. Após a conclusão do testFn, todas as suas variáveis ​​locais seriam simplesmente removidas da pilha.

Portanto, outro conceito foi proposto - o conceito de memória alocada dinamicamente (heap, heep) + coletor de lixo + contagem de referência. A essência desse conceito é simples: desde que haja pelo menos uma referência a um objeto, ele não será excluído da memória. Mais detalhes podem ser encontrados aqui .

Encerramento (Encerramentos)


Um fechamento é uma combinação de um bloco de código e dados do contexto em que esse bloco é gerado, ou seja, é o relacionamento da entidade com os contextos geradores através de uma cadeia de LO ou SopeChain.

Deixe-me citar um artigo muito bom sobre esse assunto:

 function person() { let name = 'Peter'; return function displayName() { console.log(name); }; } let peter = person(); peter(); // prints 'Peter' 

Quando a função de pessoa é executada, o JavaScript cria um novo contexto de execução e o ambiente lexical para a função. Após a conclusão desta função, ela retornará a função displayName e será atribuída à variável peter.

Assim, seu ambiente lexical ficará assim:

 personLexicalEnvironment = { environmentRecord: { name : 'Peter', displayName: < displayName function reference> } outer: <globalLexicalEnvironment> } 

Quando a função de pessoa é concluída, seu contexto de execução é retirado da pilha. Mas seu ambiente lexical ainda permanecerá na memória, pois o ambiente lexical de sua função displayName interna se refere a ele. Assim, suas variáveis ​​ainda estarão disponíveis na memória.

Quando a função peter é executada (que na verdade é uma referência à função displayName), o JavaScript cria um novo contexto de execução e um ambiente lexical para essa função.

Portanto, seu ambiente lexical ficará assim:

 displayNameLexicalEnvironment = { environmentRecord: { } outer: <personLexicalEnvironment> } 

Não há variável na função displayName, seu registro de ambiente estará vazio. Durante a execução desta função, o JavaScript tentará encontrar a variável name no ambiente lexical da função.

Como não há variáveis ​​no ambiente lexical da função displayName, ele procurará no ambiente lexical externo, ou seja, o ambiente lexical da função person, que ainda está na memória. O JavaScript encontrará essa variável e o nome será impresso no console.
A característica mais importante de um fechamento no ES é que ele usa um escopo estático (em vários outros idiomas que usam o fechamento, a situação é diferente).

Um exemplo:

 var a = 5; function testFn() { alert(a); } (function(funArg) { var a = 20; funArg();//  5 ..  ScopeChain/LexicalEnvironment testFn   ,    = 5 })(testFn) 

Outra propriedade de fechamento importante é a seguinte situação:

 var first; var second; function testFn() { var a = 10; first = function() { return ++a; } second = function() { return --a; } a = 2; first();//3 } testFn(); first();//4 second();//3 

I.e. vemos que a variável livre presente no fechamento de várias funções é alterada por referência por elas.

Conclusão


Na estrutura deste artigo, descrevemos brevemente dois conceitos centrais para o EcmaScript: ambiente lexical e fechamento. De fato, esses dois tópicos são muito mais amplos. Se a comunidade quiser obter uma descrição mais aprofundada das diferenças entre os diferentes tipos de ambientes lexicais ou aprender como a v8 cria um fechamento, escreva sobre isso nos comentários.

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


All Articles