Um amigo meu e eu estamos discutindo o que é um fechamento em JS e o que não é. Nós apenas queremos ter certeza de que realmente entendemos corretamente.
Vamos pegar este exemplo. Temos um loop de contagem e queremos imprimir a variável do contador no console atrasada. Portanto, usamos setTimeout
e closures para capturar o valor da variável do contador para garantir que ela não imprima N vezes o valor N.
A solução errada sem fechamentos ou qualquer coisa próxima a fechamentos seria:
for(var i = 0; i < 10; i++) {
setTimeout(function() {
console.log(i);
}, 1000);
}
que obviamente imprimirá 10 vezes o valor de i
após o loop, ou seja, 10.
Então, sua tentativa foi:
for(var i = 0; i < 10; i++) {
(function(){
var i2 = i;
setTimeout(function(){
console.log(i2);
}, 1000)
})();
}
imprimindo de 0 a 9 conforme o esperado.
Eu disse a ele que ele não está usando um fechamento para capturar i
, mas ele insiste que está. Eu provei que ele não usa fechamentos colocando o corpo do loop for dentro de outro setTimeout
(passando sua função anônima para setTimeout
), imprimindo 10 vezes 10 novamente. O mesmo se aplica se eu armazenar sua função em a var
e executá-la após o loop, também imprimindo 10 vezes 10. Portanto, meu argumento é que ele realmente não captura o valori
, tornando sua versão não um fechamento.
Minha tentativa foi:
for(var i = 0; i < 10; i++) {
setTimeout((function(i2){
return function() {
console.log(i2);
}
})(i), 1000);
}
Então eu capturo i
(nomeado i2
dentro do fechamento), mas agora retorno outra função e passo adiante. No meu caso, a função passada para setTimeout realmente captura i
.
Agora quem está usando fechos e quem não está?
Observe que as duas soluções imprimem de 0 a 9 no console atrasadas, portanto resolvem o problema original, mas queremos entender qual dessas duas soluções usa fechamentos para fazer isso.
fonte
Respostas:
Nota do Editor: Todas as funções em JavaScript são encerramentos, conforme explicado nesta publicação . No entanto, estamos interessados apenas em identificar um subconjunto dessas funções que são interessantes do ponto de vista teórico. Doravante, qualquer referência à palavra encerramento se referirá a este subconjunto de funções, a menos que indicado de outra forma.
Uma explicação simples para fechamentos:
Agora vamos usar isso para descobrir quem usa fechamentos e quem não usa (por uma questão de explicação, nomeei as funções):
Caso 1: Programa do seu amigo
No programa acima, existem duas funções:
f
eg
. Vamos ver se são fechamentos:Para
f
:i2
é uma variável local .i
é uma variável livre .setTimeout
é uma variável livre .g
é uma variável local .console
é uma variável livre .i
está vinculado ao escopo global.setTimeout
está vinculado ao escopo global.console
está vinculado ao escopo global.i
não é fechado porf
.setTimeout
não é fechado porf
.console
não é fechado porf
.Portanto, a função
f
não é um fechamento.Para
g
:console
é uma variável livre .i2
é uma variável livre .console
está vinculado ao escopo global.i2
está vinculado ao escopo def
.setTimeout
.console
não é fechado porg
.i2
é fechado porg
.Portanto, a função
g
é um fechamento para a variável livrei2
(que é um valor superiorg
) quando é referenciada de dentrosetTimeout
.Ruim para você: seu amigo está usando um fechamento. A função interna é um fechamento.
Caso 2: seu programa
No programa acima, existem duas funções:
f
eg
. Vamos ver se são fechamentos:Para
f
:i2
é uma variável local .g
é uma variável local .console
é uma variável livre .console
está vinculado ao escopo global.console
não é fechado porf
.Portanto, a função
f
não é um fechamento.Para
g
:console
é uma variável livre .i2
é uma variável livre .console
está vinculado ao escopo global.i2
está vinculado ao escopo def
.setTimeout
.console
não é fechado porg
.i2
é fechado porg
.Portanto, a função
g
é um fechamento para a variável livrei2
(que é um valor superiorg
) quando é referenciada de dentrosetTimeout
.Bom para você: você está usando um fechamento. A função interna é um fechamento.
Então você e seu amigo estão usando fechos. Pare de argumentar. Espero ter esclarecido o conceito de fechamento e como identificá-lo para vocês dois.
Edit: Uma explicação simples de por que todos os fechamentos de funções (créditos @ Peter):
Primeiro, vamos considerar o seguinte programa (é o controle ):
lexicalScope
eregularFunction
não são fechamentos da definição acima .message
ser alertados porqueregularFunction
não é um fechamento (ou seja, ele tem acesso a todas as variáveis em seu escopo pai - inclusivemessage
).message
é realmente alertado.Em seguida, vamos considerar o seguinte programa (é a alternativa ):
closureFunction
é um fechamento da definição acima .message
não ser alertados porqueclosureFunction
é um fechamento (ou seja, ele só tem acesso a todas as suas variáveis não locais no momento em que a função é criada ( veja esta resposta ) - isso não incluimessage
).message
realmente está sendo alertado.O que deduzimos disso?
fonte
g
é executado no escopo desetTimeout
, mas no caso 2, diz quef
é executado no escopo global. Ambos estão dentro de setTimeout, então qual é a diferença?De acordo com a
closure
definição:Você está usando
closure
se definir uma função que utilize uma variável definida fora da função. (chamamos a variável de variável livre ).Todos eles usam
closure
(mesmo no 1º exemplo).fonte
i2
que é definida fora.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;
chamando f impressões
Vamos agora considerar o caso de termos uma função
g
definida dentro de outra funçãof
.Vamos chamar
f
o pai lexical deg
. Como explicado antes, agora temos 2 escopos; o escopof
e o escopog
.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 paif
.Considere seguir;
chamando f impressões
Vamos olhar para a linha
console.log(foo);
. Neste ponto, estamos no escopog
e tentamos acessar a variávelfoo
declarada no escopof
. Mas, como afirmado anteriormente, podemos acessar qualquer variável declarada em uma função pai lexical, que é o caso aqui;g
é o pai lexical def
. Portanto,hello
é impresso.Vamos agora olhar para a linha
console.log(bar);
. Neste ponto, estamos no escopof
e tentamos acessar a variávelbar
declarada no escopog
.bar
não é declarado no escopo atual e a funçãog
não é o pai def
, portanto,bar
é indefinidaNa 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
em seguida,
h
seria capaz de acessar todas as variáveis declaradas no escopo da funçãoh
,g
ef
. 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;
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 propriedadefoo
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;
Tudo bem, vamos ver o que acontece com a primeira solução com JavaScript-No-Closure;
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;
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
ei
seria ligam aowindow
objeto e, portanto, acessível através dowindow
objeto em JavaScript e JavaScript-No-Encerramento.fonte
i
ser indefinido? Você acabou de consultar o escopo pai, que ainda é válido se não houver fechamentos.i2
comoi
no momento em que define sua função. Isso tornai
NÃO uma variável livre. Ainda assim, consideramos sua função um fechamento, mas sem nenhuma variável livre, esse é o ponto.Nunca fiquei feliz com a maneira como alguém explica isso.
A chave para entender os fechamentos é entender como seria o JS sem os fechamentos.
Sem fechamentos, isso geraria um erro
Depois que outerFunc retornar em uma versão imaginária do JavaScript desativada por fechamento, a referência a outerVar será coletada como lixo e deixada sem deixar nada para a função interna fazer referência.
Os fechamentos são essencialmente as regras especiais que entram em ação e possibilitam a existência desses vars quando uma função interna faz referência a variáveis de uma função externa. Com os fechamentos, os vars referenciados são mantidos mesmo após a função externa ser concluída ou 'fechada', se isso ajudar a lembrar o ponto.
Mesmo com fechamentos, o ciclo de vida de vars locais em uma função sem funções internas que fazem referência a seus locais funciona da mesma maneira que faria em uma versão sem fechamento. Quando a função é concluída, os locais recebem o lixo coletado.
Depois de ter uma referência em uma função interna a uma variável externa, no entanto, é como se um batente de porta fosse colocado no caminho da coleta de lixo para os vars referenciados.
Uma maneira talvez mais precisa de observar fechamentos é que a função interna basicamente usa o escopo interno como sua própria fundação de escopo.
Mas o contexto mencionado é, de fato, persistente, não como um instantâneo. O acionamento repetido de uma função interna retornada que continua incrementando e registrando o var local de uma função externa continuará alertando valores mais altos.
fonte
Vocês dois estão usando fechamentos.
Vou com a definição da Wikipedia aqui:
A tentativa de seu amigo usa claramente a variável
i
, que não é local, pegando seu valor e fazendo uma cópia para armazenar no locali2
.Sua própria tentativa passa
i
(que no site de chamada está no escopo) para uma função anônima como argumento. Até agora, este não é um fechamento, mas essa função retorna outra função que faz referência à mesmai2
. Como a função anônima internai2
não é local, isso cria um fechamento.fonte
i
parai2
, depois define alguma lógica e executa essa função. Se eu não o executasse imediatamente, mas o armazenasse em um var e o executasse após o loop, imprimiria 10, não? Portanto, não capturou i.i
muito bem. O comportamento que você está descrevendo não é resultado de fechamento versus não fechamento; é o resultado da alteração da variável fechada nesse meio tempo. Você está fazendo a mesma coisa usando sintaxe diferente, chamando imediatamente uma função e passandoi
como argumento (que copia seu valor atual no local). Se você colocar o seusetTimeout
dentro de outro,setTimeout
a mesma coisa acontecerá.Você e seu amigo usam fechos:
Na função de código do seu amigo,
function(){ console.log(i2); }
definida dentro do fechamento da função anônimafunction(){ var i2 = i; ...
e pode ler / gravar variável locali2
.No seu código, a função é
function(){ console.log(i2); }
definida dentro do fechamento da funçãofunction(i2){ return ...
e pode ler / escrever localmente valiosoi2
(declarado neste caso como um parâmetro).Nos dois casos, a função
function(){ console.log(i2); }
passou parasetTimeout
.Outro equivalente (mas com menos utilização de memória) é:
fonte
Fecho
Um fechamento não é uma função e nem uma expressão. Ele deve ser visto como um tipo de 'instantâneo' das variáveis usadas fora do escopo de funções e usadas dentro da função. Gramaticalmente, deve-se dizer: 'faça o fechamento das variáveis'.
Novamente, em outras palavras: Um fechamento é uma cópia do contexto relevante de variáveis das quais a função depende.
Mais uma vez (naïf): Um fechamento está tendo acesso a variáveis que não estão sendo passadas como parâmetro.
Lembre-se de que esses conceitos funcionais dependem fortemente da linguagem / ambiente de programação que você usa. Em JavaScript, o fechamento depende do escopo lexical (o que é verdade na maioria dos idiomas c).
Portanto, retornar uma função geralmente retorna uma função anônima / sem nome. Quando a função acessa variáveis, não passadas como parâmetro, e dentro de seu escopo (lexical), um fechamento é realizado.
Então, com relação aos seus exemplos:
Todos estão usando fechamentos. Não confunda o ponto de execução com fechamentos. Se o 'instantâneo' dos fechamentos for tirado no momento errado, os valores podem ser inesperados, mas certamente um fechamento será feito!
fonte
Vejamos as duas maneiras:
Declara e executa imediatamente uma função anônima que é executada
setTimeout()
em seu próprio contexto. O valor atual dei
é preservado fazendo uma cópiai2
primeiro; funciona por causa da execução imediata.Declara um contexto de execução para a função interna na qual o valor atual de
i
é preservadoi2
; essa abordagem também usa execução imediata para preservar o valor.Importante
Deve-se mencionar que a semântica de execução NÃO é a mesma entre as duas abordagens; sua função interna é transmitida,
setTimeout()
enquanto a função internasetTimeout()
se chama .O agrupamento de ambos os códigos dentro de outro
setTimeout()
não prova que apenas a segunda abordagem use encerramentos, simplesmente não há a mesma coisa para começar.Conclusão
Ambos os métodos usam encerramentos, então tudo se resume ao gosto pessoal; a segunda abordagem é mais fácil de "mover" ou generalizar.
fonte
setTimeout()
?i
pode ser alterada sem afetar o que a função deve imprimir, não dependendo de onde ou quando a executamos.()
, passando uma função e você verá 10 vezes a saída10
.()
é exatamente isso que faz o código dele funcionar, assim como o seu(i)
; você não apenas quebrou o código dele, fez alterações nele. Portanto, você não pode mais fazer uma comparação válida.Eu escrevi isso há um tempo atrás para me lembrar do que é um fechamento e como ele funciona no JS.
Um fechamento é uma função que, quando chamada, usa o escopo em que foi declarado, não o escopo em que foi chamado. No javaScript, todas as funções se comportam assim. Os valores variáveis em um escopo persistem, desde que haja uma função que ainda os aponte. A exceção à regra é 'this', que se refere ao objeto em que a função está dentro quando é chamada.
fonte
Depois de inspecionar atentamente, parece que vocês dois estão usando o fechamento.
No caso dos seus amigos,
i
é acessado na função anônima 1 ei2
na função anônima 2, ondeconsole.log
está presente.No seu caso, você está acessando
i2
dentro da função anônima ondeconsole.log
está presente. Adicione umadebugger;
declaração antesconsole.log
e nas ferramentas do desenvolvedor do Chrome, em "Variáveis de escopo", que informará sob qual escopo a variável é.fonte
Considere o seguinte. Isso cria e recria uma função
f
que fechai
, mas diferentes !:enquanto o seguinte se fecha sobre "uma" função "em si"
(elas mesmas! o trecho depois disso usa um único referente
f
)ou para ser mais explícito:
NB a última definição de
f
éfunction(){ console.log(9) }
antes0
é impressa.Embargo! O conceito de fechamento pode ser uma distração coercitiva da essência da programação elementar:
x-refs .:
Como funcionam os fechamentos de JavaScript?
Explicação sobre fechamentos Javascript Um fechamento
(JS) requer uma função dentro de uma função
Como entender fechamentos em Javascript?
Confusão de variáveis locais e globais em Javascript
fonte
Run' only was desired - not sure how to remove the
Copiar`Gostaria de compartilhar meu exemplo e uma explicação sobre fechamentos. Fiz um exemplo em python e duas figuras para demonstrar os estados da pilha.
A saída desse código seria a seguinte:
Aqui estão duas figuras para mostrar pilhas e o fechamento anexado ao objeto de função.
quando a função é retornada do criador
quando a função é chamada mais tarde
Quando a função é chamada por meio de um parâmetro ou de uma variável não-local, o código precisa de ligações de variáveis locais como margin_top, padding e também a, b, n. Para garantir que o código de função funcione, o quadro de pilha da função maker que desapareceu há muito tempo deve estar acessível, com backup no fechamento que podemos encontrar junto com o objeto de mensagem de função.
fonte
delete
link abaixo da resposta.