Em poucas palavras JavaScript Closures permitem uma função para acessar uma variável que é declarada em uma função lexical-pai .
Vamos ver uma explicação mais detalhada. Para entender os fechamentos, é importante entender como o JavaScript define as variáveis.
Escopos
No JavaScript, os escopos são definidos com funções. Toda função define um novo escopo.
Considere o seguinte exemplo;
function f()
{//begin of scope f
var foo='hello'; //foo is declared in scope f
for(var i=0;i<2;i++){//i is declared in scope f
//the for loop is not a function, therefore we are still in scope f
var bar = 'Am I accessible?';//bar is declared in scope f
console.log(foo);
}
console.log(i);
console.log(bar);
}//end of scope f
chamando f impressões
hello
hello
2
Am I Accessible?
Vamos agora considerar o caso de termos uma função g
definida dentro de outra função f
.
function f()
{//begin of scope f
function g()
{//being of scope g
/*...*/
}//end of scope g
/*...*/
}//end of scope f
Vamos chamar f
o pai lexical de g
. Como explicado antes, agora temos 2 escopos; o escopo f
e o escopo g
.
Mas um escopo está "dentro" do outro, então o escopo da função filho faz parte do escopo da função pai? O que acontece com as variáveis declaradas no escopo da função pai; poderei acessá-los no escopo da função filho? É exatamente aí que os fechamentos entram em cena.
Encerramentos
Em JavaScript, a função g
não pode acessar apenas as variáveis declaradas no escopo, g
mas também acessar as variáveis declaradas no escopo da função pai f
.
Considere seguir;
function f()//lexical parent function
{//begin of scope f
var foo='hello'; //foo declared in scope f
function g()
{//being of scope g
var bar='bla'; //bar declared in scope g
console.log(foo);
}//end of scope g
g();
console.log(bar);
}//end of scope f
chamando f impressões
hello
undefined
Vamos olhar para a linha console.log(foo);
. Neste ponto, estamos no escopo g
e tentamos acessar a variável foo
declarada no escopo f
. Mas, como afirmado anteriormente, podemos acessar qualquer variável declarada em uma função pai lexical, que é o caso aqui; g
é o pai lexical de f
. Portanto, hello
é impresso.
Vamos agora olhar para a linha console.log(bar);
. Neste ponto, estamos no escopo f
e tentamos acessar a variável bar
declarada no escopo g
. bar
não é declarado no escopo atual e a função g
não é o pai de f
, portanto, bar
é indefinida
Na verdade, também podemos acessar as variáveis declaradas no escopo de uma função lexical de "grand parent". Portanto, se houver uma função h
definida dentro da funçãog
function f()
{//begin of scope f
function g()
{//being of scope g
function h()
{//being of scope h
/*...*/
}//end of scope h
/*...*/
}//end of scope g
/*...*/
}//end of scope f
em seguida, h
seria capaz de acessar todas as variáveis declaradas no escopo da função h
, g
e f
. Isso é feito com fechamentos . Nos fechamentos de JavaScript, podemos acessar qualquer variável declarada na função pai lexical, na função pai genérico lexical, na função pai bisavô lexical, etc. Isso pode ser visto como uma cadeia de escopo ; scope of current function -> scope of lexical parent function -> scope of lexical grand parent function -> ...
até a última função pai que não tem pai lexical.
O objeto da janela
Na verdade, a cadeia não para na última função pai. Há mais um escopo especial; o escopo global . Toda variável não declarada em uma função é considerada declarada no escopo global. O escopo global possui duas especialidades;
- toda variável declarada no escopo global é acessível em qualquer lugar
- as variáveis declaradas no escopo global correspondem às propriedades do
window
objeto.
Portanto, existem exatamente duas maneiras de declarar uma variável foo
no escopo global; não declarando-o em uma função ou configurando a propriedade foo
do objeto de janela.
Ambas as tentativas usam fechamentos
Agora que você leu uma explicação mais detalhada, agora pode ser aparente que ambas as soluções usam fechamentos. Mas, com certeza, vamos fazer uma prova.
Vamos criar uma nova linguagem de programação; JavaScript sem fechamento. Como o nome sugere, o JavaScript sem fechamento é idêntico ao JavaScript, exceto que ele não suporta Closures.
Em outras palavras;
var foo = 'hello';
function f(){console.log(foo)};
f();
//JavaScript-No-Closure prints undefined
//JavaSript prints hello
Tudo bem, vamos ver o que acontece com a primeira solução com JavaScript-No-Closure;
for(var i = 0; i < 10; i++) {
(function(){
var i2 = i;
setTimeout(function(){
console.log(i2); //i2 is undefined in JavaScript-No-Closure
}, 1000)
})();
}
portanto, isso será impresso undefined
10 vezes em JavaScript-No-Closure.
Portanto, a primeira solução usa fechamento.
Vamos olhar para a segunda solução;
for(var i = 0; i < 10; i++) {
setTimeout((function(i2){
return function() {
console.log(i2); //i2 is undefined in JavaScript-No-Closure
}
})(i), 1000);
}
portanto, isso será impresso undefined
10 vezes em JavaScript-No-Closure.
Ambas as soluções usam fechamentos.
Editar: Supõe-se que esses três trechos de código não estejam definidos no escopo global. Caso contrário, as variáveis foo
e i
seria ligam ao window
objeto e, portanto, acessível através do window
objeto em JavaScript e JavaScript-No-Encerramento.