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
outer
que tem uma variávelb
, e devolve oinner
function - an inner function
inner
que tem a sua variável chamadaa
, e acede a umaouter
variá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 inner
procura 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,X
irá 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 inner
devolve 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 quea
existe desde que acabou de o criar. No entanto, a variávelb
já não existe. Uma vez queb
faz parte da função externa,b
só 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 doouter
função deixam de existir, e portanto a variávelb
já 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
outer
variá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 para10
Variávelc
é criado, e definir para100
br> Chamemos a istob(first_time)
ec(first_time)
para nossa própria referência. - O
inner
função é devolvido e atribuído aX
neste ponto, a variávelb
está incluída noinner
cadeia de âmbito da função como um fecho comb=10
, uma vez queinner
utiliza a variávelb
. - A função
outer
completa a execução, e todas as suas variáveis deixam de existir. A variávelc
já não existe, embora a variávelb
exista como um fecho dentro deinner
.
var Y= outer(); // outer() invoked the second time
- Variável
b
é criada de novo e é definida para10
Variávelc
é criada de novo.
Nota que emboraouter()
tenha sido executada uma vez antes das variáveisb
ec
tenha 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 aY
Neste ponto a variávelb
está incluído noinner
cadeia de alcance da função como um fecho comb(second_time)=10
, desdeinner
utiliza a variávelb
. - A função
outer
completa 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
a
eb
sã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 para20
Qualquer valor anterior da variávela
já 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
é retirado do valor de fecho –b(first_time)
Notemos também que tínhamos aumentado o valor deb
por1
desde a execução anterior, sob=11
- variables
a
eb
são incrementados por1
novamente -
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 para20
Um valor anterior da variávela
já 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 deb
por1
na execução anterior, sob=12
- variables
a
eb
são incrementados por1
novamente -
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
a
eb
sã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