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);
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;
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();
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();
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();
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.