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 gdefinida 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 fo pai lexical de g. Como explicado antes, agora temos 2 escopos; o escopo fe 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 gnão pode acessar apenas as variáveis declaradas no escopo, gmas 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 ge tentamos acessar a variável foodeclarada 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 fe tentamos acessar a variável bardeclarada no escopo g. barnão é declarado no escopo atual e a função gnã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 hdefinida 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, hseria capaz de acessar todas as variáveis declaradas no escopo da função h, ge 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
windowobjeto.
Portanto, existem exatamente duas maneiras de declarar uma variável foono escopo global; não declarando-o em uma função ou configurando a propriedade foodo 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 undefined10 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 undefined10 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 fooe iseria ligam ao windowobjeto e, portanto, acessível através do windowobjeto em JavaScript e JavaScript-No-Encerramento.