Uma simples explicação dos encerramentos JavaScript

Os callbacks, manipuladores de eventos, funções de ordem superior podem aceder a variáveis de âmbito exterior graças aos encerramentos. Os encerramentos são importantes na programação funcional, e são frequentemente solicitados durante a entrevista de codificação JavaScript.

Embora sejam utilizados em todo o lado, os encerramentos são difíceis de apreender. Se ainda não teve o seu momento “Aha!” para compreender os encerramentos, então este post é para si.

Começarei com os termos fundamentais: alcance e alcance lexical. Depois, depois de compreender o básico, precisará apenas de um passo para finalmente compreender os encerramentos.

Antes de começar, sugiro que resista ao impulso de saltar as secções de escopo e de escopo léxico. Estes conceitos são cruciais para os encerramentos, e se os conseguirmos bem, a ideia de encerramento torna-se evidente por si mesma.

1. o âmbito

Quando se define uma variável, quer-se que seja acessível dentro de alguns limites. Por exemplo, uma variável result faz sentido existir dentro de uma função calculate(), como um detalhe interno. Fora da variável calculate(), a variável result é inútil.

A acessibilidade das variáveis é gerida por âmbito. É livre de aceder à variável definida dentro do seu âmbito. Mas fora desse âmbito, a variável é inacessível.

Em JavaScript, é criado um escopo por uma função ou bloco de código.

Vejamos como o âmbito afecta a disponibilidade de uma variável count. Esta variável pertence a um âmbito criado pela função foo():

function foo() { // The function scope let count = 0; console.log(count); // logs 0}foo();console.log(count); // ReferenceError: count is not defined

count é livremente acessível dentro do âmbito de foo().

No entanto, fora do âmbito de foo() é inacessível. Se tentar aceder count a partir do exterior de qualquer forma, o JavaScript lança ReferenceError: count is not defined.

Em JavaScript, o âmbito diz: se tiver definido uma variável dentro de uma função ou bloco de código, então pode utilizar esta variável apenas dentro dessa função ou bloco de código. O exemplo acima demonstra este comportamento.

O JavaScript scope

Agora, vejamos uma formulação geral:

O scope é uma política de espaço que rege a acessibilidade das variáveis.

Surge uma propriedade imediata: o âmbito isola as variáveis. Isso é óptimo porque âmbitos diferentes podem ter variáveis com o mesmo nome.

É possível reutilizar nomes de variáveis comuns (countindexcurrentvalue, etc.) em âmbitos diferentes sem colisões.

foo() e bar() os âmbitos de funções têm as suas próprias, mas com o mesmo nome, variáveis count:

function foo() { // "foo" function scope let count = 0; console.log(count); // logs 0}function bar() { // "bar" function scope let count = 1; console.log(count); // logs 1}foo();bar();

count variáveis de foo() e bar() os escopos de funções não colidem.

2. Âmbitos de nidificação

Vamos brincar um pouco mais com os âmbitos, e colocar um âmbito em outro.

A função innerFunc() está aninhada dentro de uma função externa outerFunc().

Os âmbitos JavaScript podem ser aninhados

Como é que os 2 âmbitos de função interagiriam entre si? Posso aceder à variável outerVar de outerFunc() de dentro de innerFunc() âmbito?

p> Vamos tentar que no exemplo:

function outerFunc() { // the outer scope let outerVar = 'I am outside!'; function innerFunc() { // the inner scope console.log(outerVar); // => logs "I am outside!" } innerFunc();}outerFunc();

Indeed, outerVar variável é acessível dentro de escopo. As variáveis do escopo externo são acessíveis dentro do escopo interno.

Agora sabe 2 coisas interessantes:

  • Escopos podem ser aninhados
  • As variáveis do âmbito exterior são acessíveis dentro do âmbito interior

3. O âmbito léxico

Como é que o JavaScript compreende que outerVar dentro de corresponde à variável outerVar de outerFunc()?

É porque o JavaScript implementa um mecanismo de delimitação de âmbito chamado delimitação lexical (ou delimitação estática). A delimitação do âmbito léxico significa que a acessibilidade das variáveis é determinada pela posição das variáveis no código fonte dentro dos âmbitos de nidificação.

Simpler, o escopo léxico significa que dentro do escopo interno é possível aceder a variáveis dos seus escopos externos.

Chama-se lexical (ou estático) porque o motor determina (no tempo de lexing) o aninhamento dos âmbitos apenas olhando para o código-fonte JavaScript, sem o executar.

Aqui está como o motor entende o trecho de código anterior:

  1. Posso vê-lo definir uma função outerFunc() que tem uma variável outerVar. Bom.
  2. Dentro do outerFunc(), posso ver que define uma função innerFunc().
  3. Dentro do innerFunc(), posso ver uma variável outerVar sem declaração. Uma vez que utilizo o escopo léxico, considero a variável outerVar dentro de innerFunc() como sendo a mesma variável que outerVar de outerFunc().

A ideia destilada do âmbito léxico:

O âmbito léxico consiste em âmbitos exteriores determinados estaticamente.

Por exemplo:

const myGlobal = 0;function func() { const myVar = 1; console.log(myGlobal); // logs "0" function innerOfFunc() { const myInnerVar = 2; console.log(myVar, myGlobal); // logs "1 0" function innerOfInnerOfFunc() { console.log(myInnerVar, myVar, myGlobal); // logs "2 1 0" } innerOfInnerOfFunc(); } innerOfFunc();}func();

O âmbito léxico de innerOfInnerOfFunc() consiste em âmbitos de innerOfFunc()func() e âmbito global (o âmbito mais externo). Dentro de innerOfInnerOfFunc() pode aceder às variáveis de âmbito lexical myInnerVarmyVar e myGlobal.

O âmbito léxico de innerFunc() consiste em func() e âmbito global. Dentro de innerOfFunc() pode aceder às variáveis de âmbito lexical myVar e myGlobal.

Finalmente, o âmbito léxico de func() consiste apenas no âmbito global. Dentro de func() pode aceder à variável de âmbito lexical myGlobal.

4. O fecho

Ok, o escopo léxico permite aceder estaticamente às variáveis do escopo externo. Há apenas um passo até ao encerramento!

Vejamos novamente o exemplo outerFunc() e innerFunc():

function outerFunc() { let outerVar = 'I am outside!'; function innerFunc() { console.log(outerVar); // => logs "I am outside!" } innerFunc();}outerFunc();

Dentro do âmbito , a variável outerVar é acedida a partir do âmbito léxico. Isso já é conhecido.

Nota que innerFunc() invocação acontece dentro do seu âmbito léxico (o âmbito de outerFunc()).

Vamos fazer uma alteração: innerFunc() a invocação acontece fora do seu âmbito léxico (fora de outerFunc()). Será que innerFunc() ainda poderá aceder outerVar?

Vamos fazer os ajustes ao código snippet:

function outerFunc() { let outerVar = 'I am outside!'; function innerFunc() { console.log(outerVar); // => logs "I am outside!" } return innerFunc;}const myInnerFunc = outerFunc();myInnerFunc();

Now innerFunc() é executado fora do seu âmbito léxico. E o que é importante:

ainda tem acesso a outerVar a partir do seu âmbito léxico, mesmo sendo executado fora do seu âmbito léxico.

Por outras palavras, innerFunc() fecha (a.k.a. captura, lembra-se) a variável outerVar a partir do seu âmbito léxico.

Por outras palavras, innerFunc() é um fecho porque fecha sobre a variável outerVar a partir do seu âmbito léxico.

O fecho JavaScript

P>P>P>P>Pósósós fizestes o passo final para compreender o que é um fecho:

O fecho é uma função que acede ao seu âmbito léxico mesmo executado fora do seu âmbito léxico.

Simpler, o fecho é uma função que se lembra das variáveis do local onde é definida, independentemente do local onde é executada mais tarde.

Uma regra geral para identificar um fecho: se vir numa função uma variável estranha (não definida dentro da função), o mais provável é que essa função seja um fecho porque a variável estranha é capturada.

No código anterior, outerVar é uma variável extraterrestre dentro do fecho capturada de outerFunc() âmbito.

P>Vamos continuar com exemplos que demonstram porque é que o fecho é útil.

5. Exemplos de encerramento

5.1 Manipulador de eventos

Vamos mostrar quantas vezes um botão é clicado:

let countClicked = 0;myButton.addEventListener('click', function handleClick() { countClicked++; myText.innerText = `You clicked ${countClicked} times`;});

Abrir a demonstração e clicar no botão. O texto é actualizado para mostrar o número de cliques.

Quando o botão é clicado, handleClick() é executado algures dentro do código DOM. A execução acontece longe do local de definição.

mas sendo um fecho, handleClick() captura countClicked do âmbito léxico e actualiza-o quando um clique acontece. Ainda mais, myText também é capturado.

5.2 Callbacks

Capturar variáveis do âmbito lexical é útil em callbacks.

p>A setTimeout() Callback:

const message = 'Hello, World!';setTimeout(function callback() { console.log(message); // logs "Hello, World!"}, 1000);

O callback() é um fecho porque captura a variável message.

Uma função iteradora para forEach():

let countEven = 0;const items = ;items.forEach(function iterator(number) { if (number % 2 === 0) { countEven++; }});countEven; // => 2

O iterator é um fecho porque captura a variável countEven.

5.3 Programação funcional

Currying acontece quando uma função devolve outra função até que os argumentos sejam completamente fornecidos.

Por exemplo:

function multiply(a) { return function executeMultiply(b) { return a * b; }}const double = multiply(2);double(3); // => 6double(5); // => 10const triple = multiply(3);triple(4); // => 12

multiply é uma função de curry que devolve outra função.

Currying, um conceito importante de programação funcional, também é possível graças aos encerramentos.

executeMultiply(b) é um fecho que capta a do seu âmbito léxico. Quando o fecho é invocado, a variável capturada a e o parâmetro b são utilizados para calcular a * b.

6. Conclusão

O âmbito é o que rege a acessibilidade das variáveis em JavaScript. Pode haver uma função ou um escopo de bloco.

O âmbito lexical permite que um âmbito de função aceda estaticamente às variáveis a partir dos âmbitos externos.

Finalmente, um fecho é uma função que captura variáveis do seu escopo léxico. Em palavras simples, o fecho recorda as variáveis do local onde é definido, independentemente do local onde é executado.

Closures capture variables inside event handlers, callbacks. São utilizadas na programação funcional. Além disso, poderia ser-lhe perguntado como funcionam os encerramentos durante uma entrevista de trabalho Frontend.

Todos os programadores JavaScript devem saber como funcionam os encerramentos. Lide com isso ⌐■_■.

E que tal um desafio? 7 Perguntas de Entrevista sobre Encerramentos em JavaScript. Pode responder-lhes?

Deixe uma resposta

O seu endereço de email não será publicado. Campos obrigatórios marcados com *