Fechamentos de JavaScript vs. funções anônimas

562

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 setTimeoute 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 iapó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 vare 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 i2dentro 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.

leemes
fonte
1
@leemes: Veja minha edição ninja para um segundo link.
Blender
2
nós apenas fizemos um acordo: quem está certo está indo para obter os pontos SO relacionados a esta pergunta
brillout
1
@leemes - Vocês dois estão usando fechamentos. Vocês dois fizeram duas funções - uma função externa e uma função interna; e ambas as suas funções internas são fechamentos. Todas as suas funções são lambdas ( funções anônimas ). Leia minha resposta para obter detalhes.
Aadit M Shah
1
@blesh - Não tenho idéia do que é um fechamento modificado. Vejo que seu link aponta para o código C #. Os fechamentos modificados são suportados pelo JavaScript?
Aadit M Shah

Respostas:

650

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:

  1. Tome uma função. Vamos chamá-lo de F.
  2. Liste todas as variáveis ​​de F.
  3. As variáveis ​​podem ser de dois tipos:
    1. Variáveis ​​locais (variáveis ​​ligadas)
    2. Variáveis ​​não locais (variáveis ​​livres)
  4. Se F não tiver variáveis ​​livres, não poderá ser um fechamento.
  5. Se F tem nenhum variáveis livres (que são definidas em um escopo pai de F) em seguida:
    1. Deve haver apenas um escopo pai de F ao qual uma variável livre está vinculada.
    2. Se F for referenciado fora desse escopo pai, ele se tornará um fechamento para essa variável livre.
    3. Essa variável livre é chamada de valor superior do fechamento F.

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

for (var i = 0; i < 10; i++) {
    (function f() {
        var i2 = i;
        setTimeout(function g() {
            console.log(i2);
        }, 1000);
    })();
}

No programa acima, existem duas funções: fe g. Vamos ver se são fechamentos:

Para f:

  1. Liste as variáveis:
    1. i2é uma variável local .
    2. ié uma variável livre .
    3. setTimeouté uma variável livre .
    4. gé uma variável local .
    5. consoleé uma variável livre .
  2. Encontre o escopo pai ao qual cada variável livre está vinculada:
    1. iestá vinculado ao escopo global.
    2. setTimeoutestá vinculado ao escopo global.
    3. consoleestá vinculado ao escopo global.
  3. Em qual escopo a função é referenciada ? O escopo global .
    1. Portanto, inão é fechado por f.
    2. Portanto, setTimeoutnão é fechado por f.
    3. Portanto, consolenão é fechado por f.

Portanto, a função fnão é um fechamento.

Para g:

  1. Liste as variáveis:
    1. consoleé uma variável livre .
    2. i2é uma variável livre .
  2. Encontre o escopo pai ao qual cada variável livre está vinculada:
    1. consoleestá vinculado ao escopo global.
    2. i2está vinculado ao escopo de f.
  3. Em qual escopo a função é referenciada ? O escopo desetTimeout .
    1. Portanto, consolenão é fechado por g.
    2. Portanto, i2é fechado por g.

Portanto, a função gé um fechamento para a variável livre i2(que é um valor superior g) quando é referenciada de dentro setTimeout.

Ruim para você: seu amigo está usando um fechamento. A função interna é um fechamento.

Caso 2: seu programa

for (var i = 0; i < 10; i++) {
    setTimeout((function f(i2) {
        return function g() {
            console.log(i2);
        };
    })(i), 1000);
}

No programa acima, existem duas funções: fe g. Vamos ver se são fechamentos:

Para f:

  1. Liste as variáveis:
    1. i2é uma variável local .
    2. gé uma variável local .
    3. consoleé uma variável livre .
  2. Encontre o escopo pai ao qual cada variável livre está vinculada:
    1. consoleestá vinculado ao escopo global.
  3. Em qual escopo a função é referenciada ? O escopo global .
    1. Portanto, consolenão é fechado por f.

Portanto, a função fnão é um fechamento.

Para g:

  1. Liste as variáveis:
    1. consoleé uma variável livre .
    2. i2é uma variável livre .
  2. Encontre o escopo pai ao qual cada variável livre está vinculada:
    1. consoleestá vinculado ao escopo global.
    2. i2está vinculado ao escopo de f.
  3. Em qual escopo a função é referenciada ? O escopo desetTimeout .
    1. Portanto, consolenão é fechado por g.
    2. Portanto, i2é fechado por g.

Portanto, a função gé um fechamento para a variável livre i2(que é um valor superior g) quando é referenciada de dentro setTimeout.

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();

function lexicalScope() {
    var message = "This is the control. You should be able to see this message being alerted.";

    regularFunction();

    function regularFunction() {
        alert(eval("message"));
    }
}

  1. Sabemos que ambos lexicalScopee regularFunctionnão são fechamentos da definição acima .
  2. Quando executamos o programa , esperamos message ser alertados porque regularFunction não é um fechamento (ou seja, ele tem acesso a todas as variáveis ​​em seu escopo pai - inclusive message).
  3. Quando executamos o programa , observamos que ele messageé realmente alertado.

Em seguida, vamos considerar o seguinte programa (é a alternativa ):

var closureFunction = lexicalScope();

closureFunction();

function lexicalScope() {
    var message = "This is the alternative. If you see this message being alerted then in means that every function in JavaScript is a closure.";

    return function closureFunction() {
        alert(eval("message"));
    };
}

  1. Sabemos que apenas closureFunctioné um fechamento da definição acima .
  2. Quando executamos o programa , esperamos message não ser alertados porque closureFunction é 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 inclui message).
  3. Quando executamos o programa , observamos que messagerealmente está sendo alertado.

O que deduzimos disso?

  1. Os intérpretes JavaScript não tratam os fechamentos de maneira diferente da maneira como tratam outras funções.
  2. Toda função carrega sua cadeia de escopo junto com ela. Os fechamentos não têm um ambiente de referência separado .
  3. Um fechamento é como qualquer outra função. Apenas os chamamos de fechamento quando são referenciados em um escopo fora do escopo ao qual eles pertencem, porque este é um caso interessante.
Aadit M Shah
fonte
40
Aceito porque você detalha muito, explicando muito bem o que está acontecendo. E, finalmente, agora entendi melhor o que é um fechamento, ou melhor, disse: como a ligação variável funciona em JS.
Leem 17/10
3
No caso 1, você diz que gé executado no escopo de setTimeout, mas no caso 2, diz que fé executado no escopo global. Ambos estão dentro de setTimeout, então qual é a diferença?
Rosscj2533 17/10/12
9
Você poderia indicar suas fontes para isso? Eu nunca vi uma definição em que uma função poderia ser um fechamento se chamada em um escopo, mas não em outro. Portanto, essa definição parece um subconjunto da definição mais geral a que estou acostumado (veja a resposta de kev ) em que um fechamento é um fechamento é um fechamento, independentemente do escopo que é chamado ou mesmo que nunca seja chamado!
Briguy37
11
@AaditMShah Concordo com você sobre o que é um fechamento, mas você fala como se houvesse uma diferença entre funções regulares e encerramentos em JavaScript. Não há diferença; internamente, cada função carrega consigo uma referência à cadeia de escopo específica em que foi criada. O mecanismo JS não considera um caso diferente. Não há necessidade de uma lista de verificação complicada; apenas saiba que todo objeto de função possui escopo lexical. O fato de variáveis ​​/ propriedades estarem disponíveis globalmente não torna a função menos um fechamento (é apenas um caso inútil).
Peter
13
@ Peter - Você sabe o que, você está correto. Não há diferença entre uma função regular e um fechamento. Fiz um teste para provar isso e isso resulta a seu favor: aqui está o controle e aqui está a alternativa . O que você diz faz sentido. O intérprete JavaScript precisa fazer contabilidade especial para fechamentos. Eles são simplesmente subprodutos de uma linguagem com escopo lexicamente e funções de primeira classe. Meu conhecimento estava limitado ao que eu li (o que era falso). Obrigado por me corrigir. Vou atualizar minha resposta para refletir o mesmo.
Aadit M Shah
96

De acordo com a closuredefinição:

Um "fechamento" é uma expressão (normalmente uma função) que pode ter variáveis ​​livres juntamente com um ambiente que liga essas variáveis ​​(que "fecha" a expressão).

Você está usando closurese 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).

kev
fonte
1
Como a terceira versão usa uma variável definida fora da função?
19412 Jon
1
@Jon o uso da função retornada i2que é definida fora.
Kev
1
@kev Você está usando o encerramento se definir uma função que use uma variável definida fora da função ...... então no "Caso 1: Programa do seu Amigo" da resposta "Aadit M Shah" é "função f" um fechamento? ele usa o i (variável que é definida fora da função). o escopo global faz referência a um determinante?
internals-in
54

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.

brillout.com
fonte
Por que deve iser indefinido? Você acabou de consultar o escopo pai, que ainda é válido se não houver fechamentos.
Leem 17/10
pelo mesmo motivo que foo é indefinido em JavaScript-No-Closure. <code> i </code> não é indefinido em JavaScript, graças a um recurso em JavaScript que permite acessar variáveis ​​definidas no pai lexical. Esse recurso é chamado de fechamento.
Brillout 17/10/12
Você não entendeu a diferença entre se referir a variáveis ​​já definidas e variáveis livres . Nos fechamentos, definimos variáveis ​​livres que devem ser vinculadas no contexto externo. No seu código, você acabou de definir i2 como ino momento em que define sua função. Isso torna iNÃO uma variável livre. Ainda assim, consideramos sua função um fechamento, mas sem nenhuma variável livre, esse é o ponto.
leemes
2
@leemes, eu concordo. E comparado à resposta aceita, isso realmente não mostra o que realmente está acontecendo. :)
Abel
3
acho que essa é a melhor resposta, explicando de maneira geral e simples os fechamentos e, então, entramos no caso de uso específico. obrigado!
tim Peterson
22

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

function outerFunc(){
    var outerVar = 'an outerFunc var';
    return function(){
        alert(outerVar);
    }
}

outerFunc()(); //returns inner function and fires it

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.

function outerFunc(){
    var incrementMe = 0;
    return function(){ incrementMe++; console.log(incrementMe); }
}
var inc = outerFunc();
inc(); //logs 1
inc(); //logs 2
Erik Reppen
fonte
Você está certo sobre o 'instantâneo' (eu acho, você se refere à minha resposta) com isso. Eu estava procurando por uma palavra que indicasse o comportamento. No seu exemplo, ele pode ser visto como uma construção de fechamento 'hotlink'. Ao capturar o fechamento como parâmetro na função interna, pode-se afirmar que ele se comporta como um 'instantâneo'. Mas concordo que palavras mal usadas apenas aumentam a confusão do assunto. Se você tiver alguma sugestão sobre isso, atualizarei minha resposta.
Andries
Pode ajudar na explicação se você atribuir à função interna uma função nomeada.
precisa
Sem fechamentos, você receberá um erro porque está tentando usar uma variável que não existe.
Juan Mendes
Hmm ... bom ponto. Fazer referência a um var indefinido nunca gerou um erro, pois acabaria sendo considerado uma propriedade no objeto global ou estou confuso com a atribuição a vars indefinidos?
precisa
17

Vocês dois estão usando fechamentos.

Vou com a definição da Wikipedia aqui:

Na ciência da computação, um fechamento (também fechamento lexical ou fechamento de função) é uma função ou referência a uma função junto com um ambiente de referência - uma tabela que armazena uma referência a cada uma das variáveis ​​não locais (também chamadas de variáveis ​​livres) dessa função . Um fechamento - diferente de um ponteiro de função simples - permite que uma função acesse essas variáveis ​​não locais, mesmo quando invocada fora de seu escopo lexical imediato.

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 local i2.

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 à mesma i2. Como a função anônima interna i2não é local, isso cria um fechamento.

Jon
fonte
Sim, mas acho que o ponto é como ele está fazendo isso. Ele apenas copia ipara i2, 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.
Leem 17/10
6
@leemes: Capturou imuito 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 passando icomo argumento (que copia seu valor atual no local). Se você colocar o seu setTimeoutdentro de outro, setTimeouta mesma coisa acontecerá.
19412 Jon
13

Você e seu amigo usam fechos:

Um fechamento é um tipo especial de objeto que combina duas coisas: uma função e o ambiente em que essa função foi criada. O ambiente consiste em quaisquer variáveis ​​locais que estavam no escopo no momento em que o fechamento foi criado.

MDN: https://developer.mozilla.org/en-US/docs/JavaScript/Guide/Closures

Na função de código do seu amigo, function(){ console.log(i2); }definida dentro do fechamento da função anônima function(){ var i2 = i; ...e pode ler / gravar variável local i2.

No seu código, a função é function(){ console.log(i2); }definida dentro do fechamento da função function(i2){ return ...e pode ler / escrever localmente valioso i2(declarado neste caso como um parâmetro).

Nos dois casos, a função function(){ console.log(i2); }passou para setTimeout.

Outro equivalente (mas com menos utilização de memória) é:

function fGenerator(i2){
    return function(){
        console.log(i2);
    }
}
for(var i = 0; i < 10; i++) {
    setTimeout(fGenerator(i), 1000);
}
Andrew D.
fonte
1
Não vejo por que sua solução versus a solução de meu amigo "é mais rápida e com menos utilização de memória", você poderia elaborar?
Brillout 17/10/12
3
Na sua solução, você cria 20 objetos de função (2 objetos em cada loop: 2x10 = 20). O mesmo resultado na solução do seu problema. Na solução "minha", apenas 11 objetos de função são criados: 1 antes do loop e 10 "interno" - 1 + 1x10 = 11. Como resultado - menos uso de memória e aumento de velocidade.
Andrew D.
1
Em teoria, isso seria verdade. Na prática, também: Veja este parâmetro de referência do JSPerf
Rob W
10

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:

// 1
for(var i = 0; i < 10; i++) {
    setTimeout(function() {
        console.log(i); // closure, only when loop finishes within 1000 ms,
    }, 1000);           // i = 10 for all functions
}
// 2
for(var i = 0; i < 10; i++) {
    (function(){
        var i2 = i; // closure of i (lexical scope: for-loop)
        setTimeout(function(){
            console.log(i2); // closure of i2 (lexical scope:outer function)
        }, 1000)
    })();
}
// 3
for(var i = 0; i < 10; i++) {
    setTimeout((function(i2){
        return function() {
            console.log(i2); // closure of i2 (outer scope)

        }
    })(i), 1000); // param access i (no closure)
}

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!

Andries
fonte
10

Vejamos as duas maneiras:

(function(){
    var i2 = i;
    setTimeout(function(){
        console.log(i2);
    }, 1000)
})();

Declara e executa imediatamente uma função anônima que é executada setTimeout()em seu próprio contexto. O valor atual de ié preservado fazendo uma cópia i2primeiro; funciona por causa da execução imediata.

setTimeout((function(i2){
    return function() {
        console.log(i2);
    }
})(i), 1000);

Declara um contexto de execução para a função interna na qual o valor atual de ié preservado i2; 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 interna setTimeout()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.

Ja͢ck
fonte
Penso que a diferença é: Sua solução (1) é capturar por referência, a minha (2) é capturar por valor. Nesse caso, não faz diferença, mas se eu colocasse a execução em outro setTimeout, veríamos que a solução dele tem o problema de usar o valor final de i, não o atual, enquanto o peitoril da minha mina usa o valor atual (desde que capturado pelo valor).
Leem 17/10
@leemes Vocês capturam da mesma maneira; passar uma variável via argumento ou atribuição de função é a mesma coisa ... você poderia adicionar à sua pergunta como envolveria a execução em outra setTimeout()?
Ja͢ck
deixe-me verificar isso ... Eu queria mostrar que o objeto da função pode ser transmitido e a variável original ipode ser alterada sem afetar o que a função deve imprimir, não dependendo de onde ou quando a executamos.
Leem 17/10
Aguarde, você não passou uma função para (o externo) setTimeout. Remova-os (), passando uma função e você verá 10 vezes a saída 10.
leemes
@ leemes Como mencionado anteriormente, ()é 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.
Ja͢ck
8

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.

var z = 1;
function x(){
    var z = 2; 
    y(function(){
      alert(z);
    });
}
function y(f){
    var z = 3;
    f();
}
x(); //alerts '2' 
Nat Darke
fonte
6

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 e i2na função anônima 2, onde console.logestá presente.

No seu caso, você está acessando i2dentro da função anônima onde console.logestá presente. Adicione uma debugger;declaração antes console.loge nas ferramentas do desenvolvedor do Chrome, em "Variáveis ​​de escopo", que informará sob qual escopo a variável é.

Ramesh
fonte
2
A seção "Fechamento" no painel direito é usada porque não há um nome mais específico. "Local" é uma indicação mais forte que "Encerramento".
21712 Rob Rob W
4

Considere o seguinte. Isso cria e recria uma função fque fecha i, mas diferentes !:

i=100;

f=function(i){return function(){return ++i}}(0);
alert([f,f(),f(),f(),f(),f(),f(),f(),f(),f(),f()].join('\n\n'));

f=function(i){return new Function('return ++i')}(0);        /*  function declarations ~= expressions! */
alert([f,f(),f(),f(),f(),f(),f(),f(),f(),f(),f()].join('\n\n'));

enquanto o seguinte se fecha sobre "uma" função "em si"
(elas mesmas! o trecho depois disso usa um único referente f)

for(var i = 0; i < 10; i++) {
    setTimeout( new Function('console.log('+i+')'),  1000 );
}

ou para ser mais explícito:

for(var i = 0; i < 10; i++) {
    console.log(    f = new Function( 'console.log('+i+')' )    );
    setTimeout( f,  1000 );
}

NB a última definição de fé function(){ console.log(9) } antes 0 é impressa.

Embargo! O conceito de fechamento pode ser uma distração coercitiva da essência da programação elementar:

for(var i = 0; i < 10; i++) {     setTimeout( 'console.log('+i+')',  1000 );      }

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

ekim
fonte
trechos tentados pela 1ª vez - não sei como controlar - Run' only was desired - not sure how to remove the Copiar`
ekim 15/15/15
-1

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.

def maker(a, b, n):
    margin_top = 2
    padding = 4
    def message(msg):
        print('\n * margin_top, a * n, 
            ' ‘ * padding, msg, '  * padding, b * n)
    return message

f = maker('*', '#', 5)
g = maker('', '♥’, 3)

f('hello')
g(‘good bye!')

A saída desse código seria a seguinte:

*****      hello      #####

      good bye!    ♥♥♥

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.

Eunjung Lee
fonte
Gostaria de remover esta resposta. Percebi que a pergunta não é sobre o que é encerramento, então gostaria de passar para a outra pergunta.
Eunjung Lee
2
Eu acredito que você tem a capacidade de excluir seu próprio conteúdo. Clique no deletelink abaixo da resposta.
Rory McCrossan