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 (count
index
current
value
, 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ávelouterVar
sem declaração. Uma vez que utilizo o escopo léxico, considero a variávelouterVar
dentro deinnerFunc()
como sendo a mesma variável queouterVar
deouterFunc()
.
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 myInnerVar
myVar
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?