Acabamentos em JavaScript são um desses conceitos que muitos lutam para se entenderem. No artigo seguinte, explicarei em termos claros o que é um encerramento, e conduzirei o ponto para casa usando exemplos simples de código.
Um encerramento é uma característica em JavaScript onde uma função interna tem acesso às variáveis da função externa (envolvente) – uma cadeia de escopo.
O fecho tem três cadeias de âmbito:
- tem acesso ao seu próprio âmbito – variáveis definidas entre os seus parênteses encaracolados
- tem acesso às variáveis da função externa
- tem acesso às variáveis globais
Para os não iniciados, esta definição pode parecer apenas um monte de jargão!
Mas o que é realmente um fecho?
Vejamos um exemplo de encerramento simples em JavaScript:
function outer() { var b = 10;
function inner() {
var a = 20;
console.log(a+b);
}
return inner;
}
Aqui temos duas funções:
- uma função externa
outerque tem uma variávelb, e devolve oinnerfunction - an inner function
innerque tem a sua variável chamadaa, e acede a umaoutervariávelb, dentro do seu corpo funcional
O âmbito da variável b é limitado à função outer, e o âmbito da variável a é limitado à função inner.
Deixe-nos agora invocar a função outer(), e armazenar o resultado da função outer() numa variável X. Vamos então invocar a função outer() uma segunda vez e armazená-la na variável Y.
Vejamos passo a passo o que acontece quando a função outer() é invocada pela primeira vez:
- Variável
bé criada, o seu âmbito é limitado à funçãoouter(), e o seu valor é definido para10. - A linha seguinte é uma declaração de função, portanto nada a executar.
- Na última linha,
return innerprocura uma variável chamadainner, descobre que esta variávelinneré de facto uma função, e assim retorna todo o corpo da funçãoinner. - O conteúdo devolvido pela declaração de retorno é armazenado em
X.
Thus,Xirá armazenar o seguinte:function inner() {
var a=20;
console.log(a+b);
} - Função
outer()termina a execução, e todas as variáveis no âmbito deouter()agora já não existem.
Esta última parte é importante de compreender. Quando uma função completa a sua execução, quaisquer variáveis que foram definidas dentro do âmbito da função deixam de existir.
A duração de uma variável definida dentro de uma função é a duração da execução da função.
O que isto significa é que em , a variável b só existe durante a execução da função outer(). Assim que a função outer tiver terminado a execução, a variável b já não existe.
Quando a função é executada pela segunda vez, as variáveis da função são criadas novamente, e vivem apenas até que a função termine a execução.
Assim, quando outer() é invocada pela segunda vez:
- Uma nova variável
bé criada, o seu âmbito é limitado à funçãoouter(), e o seu valor é definido para10. - A linha seguinte é uma declaração de função, portanto nada a executar.
-
return innerdevolve todo o corpo da funçãoinner. - O conteúdo devolvido pela declaração de retorno é armazenado em
Y. - Função
outer()termina a execução, e todas as variáveis dentro do âmbito deouter()agora já não existem.
O ponto importante aqui é que quando a função outer() é invocada pela segunda vez, a variável b é criada de novo. Além disso, quando a função outer() termina a execução pela segunda vez, esta nova variável b deixa novamente de existir.
Este é o ponto mais importante a realizar. As variáveis dentro das funções só passam a existir quando a função está em execução, e deixam de existir quando as funções terminam a execução.
Agora, voltemos ao nosso exemplo de código e olhemos para X e Y. Uma vez que a função outer() na execução retorna uma função, as variáveis X e Y são funções.
Isso pode ser facilmente verificado adicionando o seguinte ao código JavaScript:
console.log(typeof(X)); //X is of type function
console.log(typeof(Y)); //Y is of type function
Desde que as variáveis X e Y são funções, podemos executá-las. Em JavaScript, uma função pode ser executada adicionando () após o nome da função, como X() e Y().
quando executamos X() e Y(), estamos essencialmente a executar o inner função.
Deixe-nos examinar passo a passo o que acontece quando X() é executado da primeira vez:
- Variável
aé criado, e o seu valor é definido para20. - JavaScript tenta agora executar
a + b. Aqui é onde as coisas ficam interessantes. O JavaScript sabe queaexiste desde que acabou de o criar. No entanto, a variávelbjá não existe. Uma vez quebfaz parte da função externa,bsó existiria enquanto a funçãoouter()estiver em execução. Uma vez que oouter()função terminou a execução muito antes de termos invocadoX(), quaisquer variáveis no âmbito doouterfunção deixam de existir, e portanto a variávelbjá não existe.
Como é que o JavaScript lida com isto?
Closures
The inner function can access the variables of the enclosing function due to closures in JavaScript. Por outras palavras, a função inner preserva a cadeia de alcance da função envolvente no momento em que a função envolvente foi executada, e assim pode aceder às variáveis da função envolvente.
No nosso exemplo, a função inner tinha preservado o valor de b=10 quando a função outer() foi executada, e continuou a preservá-la (encerramento).
Refere-se agora à sua cadeia de âmbito e nota que tem o valor da variável b dentro da sua cadeia de âmbito, uma vez que tinha incluído o valor de b dentro de um fecho no ponto em que a função outer tinha sido executada.
assim, JavaScript sabe a=20 e b=10, e pode calcular a+b.
P>Pode verificar isto adicionando a seguinte linha de código ao exemplo acima:
Abrir o elemento Inspect no Google Chrome e ir para a Consola. Pode expandir o elemento para ver realmente o elemento Closure (mostrado na terceira à última linha abaixo). Note que o valor de b=10 é preservado no elemento Closure mesmo após a função outer() completar a sua execução.
Deixe-nos agora revisitar a definição de encerramentos que vimos no início e ver se agora faz mais sentido.
Então a função interna tem três cadeias de escopo:
- acesso ao seu próprio âmbito – variável
a - acesso ao
outervariáveis da função – variávelb, que incluiu - acesso a quaisquer variáveis globais que possam ser definidas
Closures in Action
Para levar para casa o ponto de encerramento, vamos aumentar o exemplo adicionando três linhas de código:
Quando executar este código, verá a seguinte saída no código console.log:
a=20 b=10
a=20 b=11
a=20 b=12
a=20 b=10
Vamos examinar este código passo a passo para ver o que está exactamente a acontecer e para ver os encerramentos em Acção!
var X = outer(); // outer() invoked the first time
A função outer() é invocada pela primeira vez. Os passos seguintes têm lugar:
- Variável
bé criado, e é definido para10Variávelcé criado, e definir para100br> Chamemos a istob(first_time)ec(first_time)para nossa própria referência. - O
innerfunção é devolvido e atribuído aXneste ponto, a variávelbestá incluída noinnercadeia de âmbito da função como um fecho comb=10, uma vez queinnerutiliza a variávelb. - A função
outercompleta a execução, e todas as suas variáveis deixam de existir. A variávelcjá não existe, embora a variávelbexista como um fecho dentro deinner.
var Y= outer(); // outer() invoked the second time
- Variável
bé criada de novo e é definida para10Variávelcé criada de novo.
Nota que emboraouter()tenha sido executada uma vez antes das variáveisbectenha deixado de existir, uma vez que a função completou a execução são criadas como variáveis totalmente novas.
Deixe-nos chamar estasb(second_time)ec(second_time)para nossa própria referência. - A função
inneré devolvida e atribuída aYNeste ponto a variávelbestá incluído noinnercadeia de alcance da função como um fecho comb(second_time)=10, desdeinnerutiliza a variávelb. - A função
outercompleta a execução, e todas as suas variáveis deixam de existir.
A variávelc(second_time)já não existe, embora a variávelb(second_time)exista como encerramento dentro deinner.
Agora vamos ver o que acontece quando as seguintes linhas de código são executadas:
X(); // X() invoked the first time
X(); // X() invoked the second time
X(); // X() invoked the third timeY(); // Y() invoked the first time
Quando X() é invocado pela primeira vez,
- variável
aé criado, e definido para20 - o valor de
a=20, o valor debé do valor de fecho.b(first_time), sob=10 - variables
aebsão incrementadas por1 -
X()completa execução e todas as suas variáveis internas – variávela– deixam de existir.
No entanto,b(first_time)foi preservada como o encerramento, portantob(first_time)continua a existir.
Quando X() é invocado pela segunda vez,
- variable
aé criado de novo, e definido para20Qualquer valor anterior da variávelajá não existe, uma vez que deixou de existir quandoX()completou a execução na primeira vez. - o valor de
a=20o valor debé retirado do valor de fecho –b(first_time)Notemos também que tínhamos aumentado o valor debpor1desde a execução anterior, sob=11 - variables
aebsão incrementados por1novamente -
X()completa execução e todas as suas variáveis internas – variável a – deixam de existir
No entanto,b(first_time)é preservado uma vez que o encerramento continua a existir.
Quando X() é invocado pela terceira vez,
- variável
aé criado de novo, e definido para20Um valor anterior da variávelajá não existe, uma vez que deixou de existir quandoX()completou a execução na primeira vez. - o valor de
a=20, o valor debé do valor de fecho –b(first_time)br> Também note-se que tínhamos incrementado o valor debpor1na execução anterior, sob=12 - variables
aebsão incrementados por1novamente -
X()completa execução, e todas as suas variáveis internas – variávela– deixam de existir
No entanto,b(first_time)é preservada à medida que o fecho continua a existir - variável
aé criado de novo, e definido para20 - o valor de
a=20, o valor debé do valor de fecho –b(second_time), sob=10 - variables
aebsão incrementados por1 -
Y()completa execução, e todas as suas variáveis internas – variávela– deixam de existir
No entanto,b(second_time)foi preservada como o fecho, por issob(second_time)continua a existir.
quando Y() é invocado pela primeira vez,
Observações finais
Acabamentos são um daqueles conceitos subtis em JavaScript que são difíceis de apreender no início. Mas uma vez entendidos, apercebemo-nos de que as coisas não poderiam ter sido de outra forma.
Esperemos que estas explicações passo-a-passo nos ajudem a compreender realmente o conceito de encerramento em JavaScript!
Outros Artigos:
- Um guia rápido para funções “auto-invocatórias” em Javascript
- Escopo da Função vs. Escopo do Bloco em Javascript
- Como usar Promessas em JavaScript
- Como construir uma animação Sprite simples em JavaScript