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.
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().
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:
- Posso vê-lo definir uma função
outerFunc()que tem uma variávelouterVar. Bom. - Dentro do
outerFunc(), posso ver que define uma funçãoinnerFunc(). - Dentro do
innerFunc(), posso ver uma variávelouterVarsem declaração. Uma vez que utilizo o escopo léxico, considero a variávelouterVardentro deinnerFunc()como sendo a mesma variável queouterVardeouterFunc().
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.
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?