Pergunta

Como você explicaria os encerramentos de JavaScript para alguém que conhece os conceitos em que consistem (por exemplo, funções, variáveis ​​e similares), mas não entende os próprios encerramentos?

Eu tenho visto o exemplo do esquema dado na Wikipedia, mas infelizmente não ajudou.

Foi útil?

Solução

Fechamentos JavaScript para iniciantes

Enviado por Morris em terça-feira, 21/02/2006 às 10:19.Editado pela comunidade desde então.

Fechamentos não são mágicos

Esta página explica os encerramentos para que um programador possa entendê-los — usando código JavaScript funcional.Não é para gurus ou programadores funcionais.

Os fechamentos são não é difícil entender uma vez que o conceito central seja grocado.No entanto, eles são impossíveis de compreender lendo quaisquer explicações teóricas ou de orientação acadêmica!

Este artigo é destinado a programadores com alguma experiência de programação em uma linguagem convencional e que possam ler a seguinte função JavaScript:

function sayHello(name) {
  var text = 'Hello ' + name;
  var say = function() { console.log(text); }
  say();
}
sayHello('Joe');

Dois breves resumos

  • Quando uma função (foo) declara outras funções (bar e baz), a família de variáveis ​​locais criadas em foo é não destruído quando a função termina.As variáveis ​​tornam-se simplesmente invisíveis para o mundo exterior. foo pode, portanto, retornar astuciosamente as funções bar e baz, e eles podem continuar a ler, escrever e comunicar uns com os outros através desta família fechada de variáveis ​​("o encerramento") na qual ninguém mais pode interferir, nem mesmo alguém que ligue foo novamente no futuro.

  • Um encerramento é uma forma de apoiar funções de primeira classe;é uma expressão que pode fazer referência a variáveis ​​dentro de seu escopo (quando foi declarada pela primeira vez), ser atribuída a uma variável, ser passada como argumento para uma função ou ser retornada como resultado de uma função.

Um exemplo de fechamento

O código a seguir retorna uma referência a uma função:

function sayHello2(name) {
  var text = 'Hello ' + name; // Local variable
  var say = function() { console.log(text); }
  return say;
}
var say2 = sayHello2('Bob');
say2(); // logs "Hello Bob"

A maioria dos programadores JavaScript entenderá como uma referência a uma função é retornada para uma variável (say2) no código acima.Caso contrário, você precisará examinar isso antes de aprender os encerramentos.Um programador usando C pensaria na função como retornando um ponteiro para uma função, e que as variáveis say e say2 cada um era um ponteiro para uma função.

Há uma diferença crítica entre um ponteiro C para uma função e uma referência JavaScript para uma função.Em JavaScript, você pode pensar em uma variável de referência de função como tendo um ponteiro para uma função também como um ponteiro oculto para um fechamento.

O código acima tem um encerramento porque a função anônima function() { console.log(text); } é declarado dentro outra função, sayHello2() neste exemplo.Em JavaScript, se você usar o function palavra-chave dentro de outra função, você está criando um encerramento.

Em C e na maioria das outras linguagens comuns, depois uma função retorna, todas as variáveis ​​locais não estão mais acessíveis porque o stack-frame foi destruído.

Em JavaScript, se você declarar uma função dentro de outra função, as variáveis ​​locais da função externa poderão permanecer acessíveis após retornar dela.Isso é demonstrado acima, porque chamamos a função say2() depois que voltamos de sayHello2().Observe que o código que chamamos faz referência à variável text, que foi um variável local da função sayHello2().

function() { console.log(text); } // Output of say2.toString();

Olhando para a saída de say2.toString(), podemos ver que o código se refere à variável text.A função anônima pode fazer referência text que contém o valor 'Hello Bob' porque as variáveis ​​locais de sayHello2() foram secretamente mantidos vivos em um encerramento.

A genialidade é que em JavaScript uma referência de função também tem uma referência secreta para o fechamento em que foi criada - semelhante a como os delegados são um ponteiro de método mais uma referência secreta para um objeto.

Mais exemplos

Por alguma razão, os encerramentos parecem realmente difíceis de entender quando você lê sobre eles, mas quando você vê alguns exemplos, fica claro como eles funcionam (demorei um pouco).Recomendo trabalhar os exemplos cuidadosamente até entender como eles funcionam.Se você começar a usar encerramentos sem entender completamente como eles funcionam, logo criará alguns bugs muito estranhos!

Exemplo 3

Este exemplo mostra que as variáveis ​​locais não são copiadas — elas são mantidas por referência.É como se o stack-frame permanecesse ativo na memória mesmo após a saída da função externa!

function say667() {
  // Local variable that ends up within closure
  var num = 42;
  var say = function() { console.log(num); }
  num++;
  return say;
}
var sayNumber = say667();
sayNumber(); // logs 43

Exemplo 4

Todas as três funções globais têm uma referência comum ao mesmo encerramento porque todos eles são declarados dentro de uma única chamada para setupSomeGlobals().

var gLogNumber, gIncreaseNumber, gSetNumber;
function setupSomeGlobals() {
  // Local variable that ends up within closure
  var num = 42;
  // Store some references to functions as global variables
  gLogNumber = function() { console.log(num); }
  gIncreaseNumber = function() { num++; }
  gSetNumber = function(x) { num = x; }
}

setupSomeGlobals();
gIncreaseNumber();
gLogNumber(); // 43
gSetNumber(5);
gLogNumber(); // 5

var oldLog = gLogNumber;

setupSomeGlobals();
gLogNumber(); // 42

oldLog() // 5

As três funções têm acesso compartilhado ao mesmo encerramento — as variáveis ​​locais de setupSomeGlobals() quando as três funções foram definidas.

Observe que no exemplo acima, se você ligar setupSomeGlobals() novamente, então um novo encerramento (stack-frame!) é criado.O velho gLogNumber, gIncreaseNumber, gSetNumber variáveis ​​são substituídas por novo funções que possuem o novo fechamento.(Em JavaScript, sempre que você declara uma função dentro de outra função, as funções internas são recriadas novamente cada momento em que a função externa é chamada.)

Exemplo 5

Este exemplo mostra que o fechamento contém quaisquer variáveis ​​locais que foram declaradas dentro da função externa antes de ela ser encerrada.Observe que a variável alice é realmente declarado após a função anônima.A função anônima é declarada primeiro e quando essa função é chamada ela pode acessar o alice variável porque alice está no mesmo escopo (JavaScript faz elevação variável).Também sayAlice()() apenas chama diretamente a referência da função retornada de sayAlice() — é exatamente igual ao que foi feito anteriormente, mas sem a variável temporária.

function sayAlice() {
    var say = function() { console.log(alice); }
    // Local variable that ends up within closure
    var alice = 'Hello Alice';
    return say;
}
sayAlice()();// logs "Hello Alice"

Complicado:Note o say variável também está dentro do fechamento e pode ser acessada por qualquer outra função que possa ser declarada dentro sayAlice(), ou pode ser acessado recursivamente dentro da função interna.

Exemplo 6

Este é um verdadeiro problema para muitas pessoas, então você precisa entendê-lo.Tenha muito cuidado se estiver definindo uma função dentro de um loop:as variáveis ​​locais do fechamento podem não agir como você imagina.

Você precisa entender o recurso de "elevação variável" em Javascript para entender este exemplo.

function buildList(list) {
    var result = [];
    for (var i = 0; i < list.length; i++) {
        var item = 'item' + i;
        result.push( function() {console.log(item + ' ' + list[i])} );
    }
    return result;
}

function testList() {
    var fnlist = buildList([1,2,3]);
    // Using j only to help prevent confusion -- could use i.
    for (var j = 0; j < fnlist.length; j++) {
        fnlist[j]();
    }
}

 testList() //logs "item2 undefined" 3 times

A linha result.push( function() {console.log(item + ' ' + list[i])} adiciona uma referência a uma função anônima três vezes à matriz de resultados.Se você não está tão familiarizado com funções anônimas, pense assim:

pointer = function() {console.log(item + ' ' + list[i])};
result.push(pointer);

Observe que quando você executa o exemplo, "item2 undefined" é registrado três vezes!Isso ocorre porque, assim como nos exemplos anteriores, existe apenas um fechamento para as variáveis ​​locais para buildList (que são result, i, list e item).Quando as funções anônimas são chamadas na linha fnlist[j]();todos eles usam o mesmo fechamento único e usam o valor atual para i e item dentro daquele fechamento (onde i tem um valor de 3 porque o loop foi concluído e item tem um valor de 'item2').Observe que estamos indexando a partir de 0, portanto item tem um valor de item2.E o i++ irá incrementar i para o valor 3.

Pode ser útil ver o que acontece quando uma declaração da variável em nível de bloco item é usado (através do let palavra-chave) em vez de uma declaração de variável com escopo de função por meio do var palavra-chave.Se essa alteração for feita, cada função anônima no array result tem fechamento próprio;quando o exemplo é executado, a saída é a seguinte:

item0 undefined
item1 undefined
item2 undefined

Se a variável i também é definido usando let em vez de var, então a saída é:

item0 1
item1 2
item2 3

Exemplo 7

Neste exemplo final, cada chamada para a função principal cria um encerramento separado.

function newClosure(someNum, someRef) {
    // Local variables that end up within closure
    var num = someNum;
    var anArray = [1,2,3];
    var ref = someRef;
    return function(x) {
        num += x;
        anArray.push(num);
        console.log('num: ' + num +
            '; anArray: ' + anArray.toString() +
            '; ref.someVar: ' + ref.someVar + ';');
      }
}
obj = {someVar: 4};
fn1 = newClosure(4, obj);
fn2 = newClosure(5, obj);
fn1(1); // num: 5; anArray: 1,2,3,5; ref.someVar: 4;
fn2(1); // num: 6; anArray: 1,2,3,6; ref.someVar: 4;
obj.someVar++;
fn1(2); // num: 7; anArray: 1,2,3,5,7; ref.someVar: 5;
fn2(2); // num: 8; anArray: 1,2,3,6,8; ref.someVar: 5;

Resumo

Se tudo parece pouco claro, então o melhor a fazer é brincar com os exemplos.Ler uma explicação é muito mais difícil do que entender exemplos.Minhas explicações sobre fechamentos e stack-frames, etc.não são tecnicamente corretos – são simplificações grosseiras destinadas a ajudar a compreender.Depois que a ideia básica estiver compreendida, você poderá aprender os detalhes mais tarde.

Pontos finais:

  • Sempre que você usa function dentro de outra função, um encerramento é usado.
  • Sempre que você usa eval() dentro de uma função, um encerramento é usado.O texto que você eval pode fazer referência a variáveis ​​locais da função, e dentro eval você pode até criar novas variáveis ​​locais usando eval('var foo = …')
  • Quando você usa new Function(…) (o Construtor de função) dentro de uma função, não cria um fechamento.(A nova função não pode fazer referência às variáveis ​​locais da função externa.)
  • Um fechamento em JavaScript é como manter uma cópia de todas as variáveis ​​locais, exatamente como estavam quando uma função foi encerrada.
  • Provavelmente é melhor pensar que um encerramento é sempre criado apenas como uma entrada para uma função, e as variáveis ​​locais são adicionadas a esse encerramento.
  • Um novo conjunto de variáveis ​​locais é mantido sempre que uma função com um fecho é chamada (dado que a função contém uma declaração de função dentro dela, e uma referência a essa função interna é devolvida ou uma referência externa é mantida para ela de alguma forma ).
  • Duas funções podem parecer ter o mesmo texto fonte, mas têm um comportamento completamente diferente devido ao seu fechamento 'oculto'.Não acho que o código JavaScript possa realmente descobrir se uma referência de função tem um encerramento ou não.
  • Se você estiver tentando fazer modificações dinâmicas no código-fonte (por exemplo: myFunction = Function(myFunction.toString().replace(/Hello/,'Hola'));), não funcionará se myFunction é um encerramento (claro, você nunca pensaria em fazer a substituição da string do código-fonte em tempo de execução, mas...).
  • É possível obter declarações de funções dentro de declarações de funções dentro de funções… e você pode obter encerramentos em mais de um nível.
  • Acho que normalmente um encerramento é um termo tanto para a função quanto para as variáveis ​​​​que são capturadas.Observe que não uso essa definição neste artigo!
  • Suspeito que os encerramentos em JavaScript sejam diferentes daqueles normalmente encontrados em linguagens funcionais.

Ligações

Obrigado

Se você tem apenas fechamentos aprendidos (aqui ou em outro lugar!), então estou interessado em qualquer feedback seu sobre quaisquer mudanças que você possa sugerir que possam tornar este artigo mais claro.Envie um e-mail para morrisjohns.com (morris_closure @).Observe que não sou um guru em JavaScript — nem em encerramentos.


A postagem original de Morris pode ser encontrada no Arquivo da Internet.

Outras dicas

Sempre que você vê a palavra-chave function dentro de outra função, a função interna tem acesso às variáveis ​​na função externa.

function foo(x) {
  var tmp = 3;

  function bar(y) {
    console.log(x + y + (++tmp)); // will log 16
  }

  bar(10);
}

foo(2);

Isso sempre registrará 16, porque bar pode acessar o x que foi definido como um argumento para foo, e também pode acessar tmp de foo.

Que é um encerramento.Uma função não precisa retornar para ser chamado de encerramento. Simplesmente acessar variáveis ​​fora do seu escopo léxico imediato cria um fechamento.

function foo(x) {
  var tmp = 3;

  return function (y) {
    console.log(x + y + (++tmp)); // will also log 16
  }
}

var bar = foo(2); // bar is now a closure.
bar(10);

A função acima também registrará 16, porque bar ainda pode referir-se x e tmp, mesmo que não esteja mais diretamente dentro do escopo.

No entanto, desde tmp ainda está por dentro bardo fechamento, ele também está sendo incrementado.Será incrementado cada vez que você ligar bar.

O exemplo mais simples de encerramento é este:

var a = 10;

function test() {
  console.log(a); // will output 10
  console.log(b); // will output 6
}
var b = 6;
test();

Quando uma função JavaScript é invocada, um novo contexto de execução é criado.Juntamente com os argumentos da função e o objeto pai, este contexto de execução também recebe todas as variáveis ​​declaradas fora dele (no exemplo acima, tanto 'a' quanto 'b').

É possível criar mais de uma função de encerramento, seja retornando uma lista delas ou configurando-as para variáveis ​​globais.Tudo isso se referirá ao mesmo x e o mesmo tmp, eles não fazem suas próprias cópias.

Aqui o número x é um número literal.Tal como acontece com outros literais em JavaScript, quando foo é chamado, o número x é copiado em foo como seu argumento x.

Por outro lado, JavaScript sempre usa referências ao lidar com objetos.Se você disser, você ligou foo com um objeto, o fechamento que ele retorna será referência aquele objeto original!

function foo(x) {
  var tmp = 3;

  return function (y) {
    console.log(x + y + tmp);
    x.memb = x.memb ? x.memb + 1 : 1;
    console.log(x.memb);
  }
}

var age = new Number(2);
var bar = foo(age); // bar is now a closure referencing age.
bar(10);

Como esperado, cada chamada para bar(10) irá incrementar x.memb.O que talvez não fosse esperado é que x está simplesmente se referindo ao mesmo objeto que o age variável!Depois de algumas ligações para bar, age.memb serão 2!Essa referência é a base para vazamentos de memória com objetos HTML.

PREFÁCIO:esta resposta foi escrita quando a pergunta era:

Como disse o velho Albert:“Se você não consegue explicar para uma criança de seis anos, você realmente não entende.”.Bem, tentei explicar os fechamentos de JS para um amigo de 27 anos e falhei completamente.

Alguém pode considerar que tenho 6 anos e estou estranhamente interessado nesse assunto?

Tenho certeza de que fui uma das únicas pessoas que tentou interpretar a pergunta inicial literalmente.Desde então, a pergunta sofreu diversas mutações, então minha resposta agora pode parecer incrivelmente boba e deslocada.Esperamos que a ideia geral da história continue divertida para alguns.


Sou um grande fã de analogias e metáforas ao explicar conceitos difíceis, então deixe-me tentar contar uma história.

Era uma vez:

Havia uma princesa...

function princess() {

Ela viveu em um mundo maravilhoso cheio de aventuras.Ela conheceu seu Príncipe Encantado, andou pelo mundo em um unicórnio, lutou contra dragões, encontrou animais falantes e muitas outras coisas fantásticas.

    var adventures = [];

    function princeCharming() { /* ... */ }

    var unicorn = { /* ... */ },
        dragons = [ /* ... */ ],
        squirrel = "Hello!";

    /* ... */

Mas ela sempre teria que voltar ao seu mundo monótono de tarefas e adultos.

    return {

E ela costumava contar-lhes sobre sua última e incrível aventura como princesa.

        story: function() {
            return adventures[adventures.length - 1];
        }
    };
}

Mas tudo o que veriam seria uma garotinha...

var littleGirl = princess();

...contando histórias sobre magia e fantasia.

littleGirl.story();

E mesmo que os adultos conhecessem princesas reais, nunca acreditariam em unicórnios ou dragões porque nunca poderiam vê-los.Os adultos diziam que eles só existiam na imaginação da menina.

Mas conhecemos a verdade real;que a menininha com a princesa dentro...

...é realmente uma princesa com uma menininha dentro.

Levando a questão a sério, deveríamos descobrir do que uma criança típica de 6 anos é capaz cognitivamente, embora admitamos que alguém interessado em JavaScript não seja tão típico.

Sobre Desenvolvimento Infantil:5 a 7 anos diz:

Seu filho será capaz de seguir instruções em duas etapas.Por exemplo, se você disser ao seu filho: “Vá até a cozinha e traga um saco de lixo para mim”, ele será capaz de se lembrar dessa direção.

Podemos usar este exemplo para explicar os encerramentos, da seguinte forma:

A cozinha é um fechamento que possui uma variável local, chamada trashBags.Existe uma função dentro da cozinha chamada getTrashBag que pega um saco de lixo e o devolve.

Podemos codificar isso em JavaScript assim:

function makeKitchen() {
  var trashBags = ['A', 'B', 'C']; // only 3 at first

  return {
    getTrashBag: function() {
      return trashBags.pop();
    }
  };
}

var kitchen = makeKitchen();

console.log(kitchen.getTrashBag()); // returns trash bag C
console.log(kitchen.getTrashBag()); // returns trash bag B
console.log(kitchen.getTrashBag()); // returns trash bag A

Outros pontos que explicam por que os fechamentos são interessantes:

  • Cada vez makeKitchen() é chamado, um novo fechamento é criado com seu próprio fechamento separado trashBags.
  • O trashBags variável é local no interior de cada cozinha e não é acessível externamente, mas a função interna no getTrashBag propriedade tem acesso a ele.
  • Cada chamada de função cria um encerramento, mas não haveria necessidade de manter o encerramento, a menos que uma função interna, que tenha acesso ao interior do encerramento, possa ser chamada de fora do encerramento.Devolvendo o objeto com o getTrashBag função faz isso aqui.

O homem de palha

Preciso saber quantas vezes um botão foi clicado e fazer algo a cada terceiro clique...

Solução bastante óbvia

// Declare counter outside event handler's scope
var counter = 0;
var element = document.getElementById('button');

element.addEventListener("click", function() {
  // Increment outside counter
  counter++;

  if (counter === 3) {
    // Do something every third time
    console.log("Third time's the charm!");

    // Reset counter
    counter = 0;
  }
});
<button id="button">Click Me!</button>

Agora, isso funcionará, mas invade o escopo externo ao adicionar uma variável, cujo único propósito é acompanhar a contagem.Em algumas situações, isso seria preferível, pois seu aplicativo externo pode precisar de acesso a essas informações.Mas neste caso, estamos apenas alterando o comportamento de cada terceiro clique, por isso é preferível coloque esta funcionalidade dentro do manipulador de eventos.

Considere esta opção

var element = document.getElementById('button');

element.addEventListener("click", (function() {
  // init the count to 0
  var count = 0;

  return function(e) { // <- This function becomes the click handler
    count++; //    and will retain access to the above `count`

    if (count === 3) {
      // Do something every third time
      console.log("Third time's the charm!");

      //Reset counter
      count = 0;
    }
  };
})());
<button id="button">Click Me!</button>

Observe algumas coisas aqui.

No exemplo acima, estou usando o comportamento de fechamento do JavaScript. Este comportamento permite que qualquer função tenha acesso ao escopo em que foi criada, indefinidamente. Para aplicar isso na prática, invoco imediatamente uma função que retorna outra função e, como a função que estou retornando tem acesso à variável de contagem interna (devido ao comportamento de fechamento explicado acima), isso resulta em um escopo privado para uso pelo resultante função...Não tão simples?Vamos diluir isso...

Um fechamento simples de uma linha

//          _______________________Immediately invoked______________________
//         |                                                                |
//         |        Scope retained for use      ___Returned as the____      |
//         |       only by returned function   |    value of func     |     |
//         |             |            |        |                      |     |
//         v             v            v        v                      v     v
var func = (function() { var a = 'val'; return function() { alert(a); }; })();

Todas as variáveis ​​fora da função retornada estão disponíveis para a função retornada, mas não estão diretamente disponíveis para o objeto de função retornado...

func();  // Alerts "val"
func.a;  // Undefined

Pegue?Portanto, em nosso exemplo principal, a variável count está contida no encerramento e sempre disponível para o manipulador de eventos, portanto, ela mantém seu estado de clique a clique.

Além disso, este estado de variável privada é completamente acessível, tanto para leituras quanto para atribuição às suas variáveis ​​de escopo privado.

Ai está;agora você está encapsulando totalmente esse comportamento.

Postagem completa no blog (incluindo considerações sobre jQuery)

Os fechamentos são difíceis de explicar porque são usados ​​para fazer funcionar algum comportamento que todos intuitivamente esperam que funcione de qualquer maneira.Eu encontro a melhor maneira de explicá-los (e a maneira como EU aprendi o que eles fazem) é imaginar a situação sem eles:

    var bind = function(x) {
        return function(y) { return x + y; };
    }
    
    var plus5 = bind(5);
    console.log(plus5(3));

O que aconteceria aqui se JavaScript não conhece fechamentos?Basta substituir a chamada na última linha pelo corpo do método (que é basicamente o que as chamadas de função fazem) e você obterá:

console.log(x + 3);

Agora, onde está a definição de x?Não o definimos no escopo atual.A única solução é deixar plus5 carregar seu escopo (ou melhor, o escopo de seu pai).Por aqui, x está bem definido e está vinculado ao valor 5.

Esta é uma tentativa de esclarecer vários (possíveis) mal-entendidos sobre fechamentos que aparecem em algumas das outras respostas.

  • Um encerramento não é criado apenas quando você retorna uma função interna. Na verdade, a função envolvente não precisa voltar de jeito nenhum para que seu fechamento seja criado.Em vez disso, você pode atribuir sua função interna a uma variável em um escopo externo ou passá-la como argumento para outra função onde ela possa ser chamada imediatamente ou a qualquer momento depois.Portanto, o encerramento da função envolvente é provavelmente criado assim que a função envolvente é chamada já que qualquer função interna tem acesso a esse fechamento sempre que a função interna é chamada, antes ou depois do retorno da função envolvente.
  • Um fechamento não faz referência a uma cópia do valores antigos de variáveis ​​em seu escopo. As próprias variáveis ​​fazem parte do fechamento e, portanto, o valor visto ao acessar uma dessas variáveis ​​é o valor mais recente no momento em que ela é acessada.É por isso que funções internas criadas dentro de loops podem ser complicadas, já que cada uma tem acesso às mesmas variáveis ​​externas em vez de obter uma cópia das variáveis ​​no momento em que a função é criada ou chamada.
  • As "variáveis" em um encerramento incluem quaisquer funções nomeadas declarado dentro da função.Eles também incluem argumentos da função.Um encerramento também tem acesso às variáveis ​​do encerramento que o contém, até o escopo global.
  • Os fechamentos usam memória, mas não causam vazamentos de memória já que o JavaScript por si só limpa suas próprias estruturas circulares que não são referenciadas.Vazamentos de memória do Internet Explorer envolvendo fechamentos são criados quando ele não consegue desconectar valores de atributos DOM que fazem referência a fechamentos, mantendo assim referências a estruturas possivelmente circulares.

OK, fã de encerramentos de 6 anos.Você quer ouvir o exemplo mais simples de encerramento?

Vamos imaginar a próxima situação:um motorista está sentado em um carro.Esse carro está dentro de um avião.O avião está no aeroporto.A capacidade do motorista de acessar coisas fora do carro, mas dentro do avião, mesmo que o avião saia do aeroporto, é um fechamento.É isso.Quando você completar 27 anos, olhe para o explicação mais detalhada ou no exemplo abaixo.

Aqui está como posso converter minha história de avião em código.

var plane = function(defaultAirport) {

  var lastAirportLeft = defaultAirport;

  var car = {
    driver: {
      startAccessPlaneInfo: function() {
        setInterval(function() {
          console.log("Last airport was " + lastAirportLeft);
        }, 2000);
      }
    }
  };
  car.driver.startAccessPlaneInfo();

  return {
    leaveTheAirport: function(airPortName) {
      lastAirportLeft = airPortName;
    }
  }
}("Boryspil International Airport");

plane.leaveTheAirport("John F. Kennedy");

A fecho é muito parecido com um objeto.Ele é instanciado sempre que você chama uma função.

O escopo de um fecho em JavaScript é lexical, o que significa que tudo o que está contido na função o fecho pertence, tem acesso a qualquer variável que esteja nele.

Uma variável está contida no fecho se você

  1. atribuí-lo com var foo=1; ou
  2. apenas escreva var foo;

Se uma função interna (uma função contida dentro de outra função) acessa tal variável sem defini-la em seu próprio escopo com var, ela modifica o conteúdo da variável no escopo externo fecho.

A fecho sobrevive ao tempo de execução da função que o gerou.Se outras funções saírem do fechamento/escopo em que são definidos (por exemplo, como valores de retorno), eles continuarão a fazer referência a esse fecho.

Exemplo

function example(closure) {
  // define somevariable to live in the closure of example
  var somevariable = 'unchanged';

  return {
    change_to: function(value) {
      somevariable = value;
    },
    log: function(value) {
      console.log('somevariable of closure %s is: %s',
        closure, somevariable);
    }
  }
}

closure_one = example('one');
closure_two = example('two');

closure_one.log();
closure_two.log();
closure_one.change_to('some new value');
closure_one.log();
closure_two.log();

Saída

somevariable of closure one is: unchanged
somevariable of closure two is: unchanged
somevariable of closure one is: some new value
somevariable of closure two is: unchanged

Escrevi uma postagem no blog há algum tempo explicando os fechamentos.Aqui está o que eu disse sobre fechamentos em termos de por que você iria querer um.

Fechamentos são uma forma de deixar uma função ter variáveis ​​privadas e persistentes - ou seja, variáveis que apenas uma função sabe sobre, onde pode Acompanhe as informações de tempos anteriores que foi executado.

Nesse sentido, eles permitem que uma função atue um pouco como um objeto com atributos privados.

Postagem completa:

Então, o que são essas coisas de fechamento?

Os fechamentos são simples:

O exemplo simples a seguir cobre todos os pontos principais dos fechamentos de JavaScript.*  

Aqui está uma fábrica que produz calculadoras que podem somar e multiplicar:

function make_calculator() {
  var n = 0; // this calculator stores a single number n
  return {
    add: function(a) {
      n += a;
      return n;
    },
    multiply: function(a) {
      n *= a;
      return n;
    }
  };
}

first_calculator = make_calculator();
second_calculator = make_calculator();

first_calculator.add(3); // returns 3
second_calculator.add(400); // returns 400

first_calculator.multiply(11); // returns 33
second_calculator.multiply(10); // returns 4000

O ponto chave: Cada chamada para make_calculator cria uma nova variável local n, que continua a ser utilizável pela calculadora dessa calculadora add e multiply funciona muito depois make_calculator retorna.

Se você está familiarizado com stack frames, estas calculadoras parecem estranhas:Como eles podem continuar acessando n depois make_calculator retorna?A resposta é imaginar que o JavaScript não usa "stack frames", mas sim "heap frames", que podem persistir após o retorno da chamada de função que os fez.

Funções internas como add e multiply, que acessam variáveis ​​declaradas em uma função externa**, são chamados fechamentos.

Isso é praticamente tudo o que há para fechar.



* Por exemplo, cobre todos os pontos do artigo "Fechos para Leigos" fornecido em outra resposta, exceto o exemplo 6, que simplesmente mostra que variáveis ​​podem ser usadas antes de serem declaradas, um fato interessante de se saber, mas completamente não relacionado a encerramentos.Também cobre todos os pontos a resposta aceita, exceto pelos pontos (1) que as funções copiam seus argumentos em variáveis ​​locais (os argumentos da função nomeada) e (2) que copiar números cria um novo número, mas copiar uma referência de objeto fornece outra referência ao mesmo objeto.Também é bom saber disso, mas, novamente, não tem nenhuma relação com fechamentos.Também é muito semelhante ao exemplo em esta resposta mas um pouco mais curto e menos abstrato.Não cobre o ponto de esta resposta ou este comentário, que é que o JavaScript dificulta a conexão do atual valor de uma variável de loop em sua função interna:A etapa de "conectar" só pode ser feita com uma função auxiliar que envolve sua função interna e é invocada em cada iteração do loop.(Estritamente falando, a função interna acessa a cópia da variável da função auxiliar, em vez de ter qualquer coisa conectada.) Novamente, muito útil ao criar fechamentos, mas não faz parte do que é um fechamento ou de como ele funciona.Há confusão adicional devido aos encerramentos funcionarem de forma diferente em linguagens funcionais como ML, onde as variáveis ​​​​são vinculadas a valores e não ao espaço de armazenamento, fornecendo um fluxo constante de pessoas que entendem os encerramentos de uma forma (ou seja, a forma de "conectar") que é simplesmente incorreto para JavaScript, onde as variáveis ​​estão sempre vinculadas ao espaço de armazenamento e nunca aos valores.

** Qualquer função externa, se várias estiverem aninhadas, ou mesmo no contexto global, como esta resposta aponta claramente.

Como eu explicaria isso para uma criança de seis anos:

Você sabe como os adultos podem ter uma casa e chamá-la de lar?Quando uma mãe tem um filho, a criança não possui nada, certo?Mas seus pais são donos de uma casa, então sempre que alguém perguntar à criança “Onde é sua casa?”, ela poderá responder “aquela casa!”, e apontar para a casa de seus pais.Um “Encerramento” é a capacidade da criança de sempre (mesmo que no exterior) poder dizer que tem uma casa, mesmo que na verdade sejam os pais os donos da casa.

Você pode explicar os fechamentos para uma criança de 5 anos?*

Eu ainda penso Explicação do Google funciona muito bem e é conciso:

/*
*    When a function is defined in another function and it
*    has access to the outer function's context even after
*    the outer function returns.
*
* An important concept to learn in JavaScript.
*/

function outerFunction(someNum) {
    var someString = 'Hey!';
    var content = document.getElementById('content');
    function innerFunction() {
        content.innerHTML = someNum + ': ' + someString;
        content = null; // Internet Explorer memory leak for DOM reference
    }
    innerFunction();
}

outerFunction(1);​

Proof that this example creates a closure even if the inner function doesn

*Uma pergunta C#

Tenho tendência a aprender melhor através de comparações BOM/RUIM.Gosto de ver código funcional seguido de código não funcional que alguém provavelmente encontrará.eu juntei um jsFiddle que faz uma comparação e tenta resumir as diferenças às explicações mais simples que consegui encontrar.

Fechamentos bem feitos:

console.log('CLOSURES DONE RIGHT');

var arr = [];

function createClosure(n) {
    return function () {
        return 'n = ' + n;
    }
}

for (var index = 0; index < 10; index++) {
    arr[index] = createClosure(index);
}

for (var index in arr) {
    console.log(arr[index]());
}
  • No código acima createClosure(n) é invocado em cada iteração do loop.Observe que eu nomeei a variável n destacar que se trata de um novo variável criada em um novo escopo de função e não é a mesma variável que index que está vinculado ao escopo externo.

  • Isso cria um novo escopo e n está vinculado a esse escopo;isso significa que temos 10 escopos separados, um para cada iteração.

  • createClosure(n) retorna uma função que retorna n dentro desse escopo.

  • Dentro de cada escopo n está vinculado a qualquer valor que tivesse quando createClosure(n) foi invocado para que a função aninhada retornada sempre retorne o valor de n que tinha quando createClosure(n) foi invocado.

Fechamentos feitos de forma errada:

console.log('CLOSURES DONE WRONG');

function createClosureArray() {
    var badArr = [];

    for (var index = 0; index < 10; index++) {
        badArr[index] = function () {
            return 'n = ' + index;
        };
    }
    return badArr;
}

var badArr = createClosureArray();

for (var index in badArr) {
    console.log(badArr[index]());
}
  • No código acima, o loop foi movido dentro do createClosureArray() function e a função agora retorna apenas o array completo, o que à primeira vista parece mais intuitivo.

  • O que pode não ser óbvio é que, desde createClosureArray() só é invocado quando apenas um escopo é criado para esta função, em vez de um para cada iteração do loop.

  • Dentro desta função, uma variável chamada index é definido.O loop é executado e adiciona funções ao array que retornam index.Observe que index é definido dentro do createClosureArray função que só é invocada uma vez.

  • Como havia apenas um escopo dentro do createClosureArray() função, index está vinculado apenas a um valor dentro desse escopo.Em outras palavras, cada vez que o loop altera o valor de index, ele altera tudo que faz referência a ele nesse escopo.

  • Todas as funções adicionadas ao array retornam o MESMO index variável do escopo pai onde foi definida, em vez de 10 diferentes de 10 escopos diferentes, como no primeiro exemplo.O resultado final é que todas as 10 funções retornam a mesma variável do mesmo escopo.

  • Após o loop terminar e index foi feito sendo modificado o valor final era 10, portanto toda função adicionada ao array retorna o valor do único index variável que agora está definida como 10.

Resultado

ENCERRAMENTOS BEM FEITOS
n = 0
n = 1
n = 2
n = 3
n = 4
n = 5
n = 6
n = 7
n = 8
n = 9

FECHAMENTOS FEITOS ERRADOS
n = 10
n = 10
n = 10
n = 10
n = 10
n = 10
n = 10
n = 10
n = 10
n = 10

Wikipedia sobre fechamentos:

Na ciência da computação, um encerramento é uma função juntamente com um ambiente de referência para os nomes não locais (variáveis ​​livres) dessa função.

Tecnicamente, em JavaScript, toda função é um fechamento.Sempre tem acesso às variáveis ​​definidas no escopo circundante.

Desde construção de definição de escopo em JavaScript é uma função, não um bloco de código como em muitas outras linguagens, o que geralmente queremos dizer com fecho em JavaScript é um função trabalhando com variáveis ​​não locais definidas em função circundante já executada.

Closures são frequentemente usados ​​para criar funções com alguns dados privados ocultos (mas nem sempre é o caso).

var db = (function() {
    // Create a hidden object, which will hold the data
    // it's inaccessible from the outside.
    var data = {};

    // Make a function, which will provide some access to the data.
    return function(key, val) {
        if (val === undefined) { return data[key] } // Get
        else { return data[key] = val } // Set
    }
    // We are calling the anonymous surrounding function,
    // returning the above inner function, which is a closure.
})();

db('x')    // -> undefined
db('x', 1) // Set x to 1
db('x')    // -> 1
// It's impossible to access the data object itself.
// We are able to get or set individual it.

ems

O exemplo acima usa uma função anônima, que foi executada uma vez.Mas não precisa ser assim.Pode ser nomeado (por ex. mkdb) e executado posteriormente, gerando uma função de banco de dados cada vez que é invocado.Cada função gerada terá seu próprio objeto de banco de dados oculto.Outro exemplo de uso de encerramentos é quando não retornamos uma função, mas um objeto contendo múltiplas funções para finalidades diferentes, cada uma dessas funções tendo acesso aos mesmos dados.

Elaborei um tutorial interativo de JavaScript para explicar como funcionam os encerramentos.O que é um fechamento?

Aqui está um dos exemplos:

var create = function (x) {
    var f = function () {
        return x; // We can refer to x here!
    };
    return f;
};
// 'create' takes one argument, creates a function

var g = create(42);
// g is a function that takes no arguments now

var y = g();
// y is 42 here

As crianças sempre se lembrarão dos segredos que compartilharam com seus pais, mesmo depois que seus pais forem sumido.Isto é o que os encerramentos são para funções.

Os segredos das funções JavaScript são as variáveis ​​privadas

var parent = function() {
 var name = "Mary"; // secret
}

Cada vez que você a chama, a variável local "nome" é criada e recebe o nome "Mary".E toda vez que a função sai a variável se perde e o nome é esquecido.

Como você pode imaginar, como as variáveis ​​são recriadas toda vez que a função é chamada e ninguém mais as conhecerá, deve haver um local secreto onde elas são armazenadas.Poderia ser chamado Câmara de segredos ou pilha ou escopo local mas isso realmente não importa.Sabemos que eles estão ali, em algum lugar, escondidos na memória.

Mas, em JavaScript existe uma coisa muito especial: funções que são criadas dentro de outras funções, também podem conhecer as variáveis ​​locais de seus pais e mantê-las enquanto viverem.

var parent = function() {
  var name = "Mary";
  var child = function(childName) {
    // I can also see that "name" is "Mary"
  }
}

Portanto, enquanto estivermos na função pai, ela poderá criar uma ou mais funções filhas que compartilhem as variáveis ​​secretas do local secreto.

Mas o triste é que, se o filho também for uma variável privada de sua função pai, ele também morreria quando o pai terminasse, e os segredos morreriam com eles.

Então para viver a criança tem que ir embora antes que seja tarde demais

var parent = function() {
  var name = "Mary";
  var child = function(childName) {
    return "My name is " + childName  +", child of " + name; 
  }
  return child; // child leaves the parent ->
}
var child = parent(); // < - and here it is outside 

E agora, mesmo que Maria “não esteja mais correndo”, a memória dela não se perde e seu filho sempre se lembrará de seu nome e de outros segredos que compartilharam durante o tempo que passaram juntos.

Então, se você chamar a criança de “Alice”, ela vai responder

child("Alice") => "My name is Alice, child of Mary"

Isso é tudo que há para contar.

Não entendo por que as respostas são tão complexas aqui.

Aqui está um encerramento:

var a = 42;

function b() { return a; }

Sim.Você provavelmente usa isso muitas vezes ao dia.


Não há razão para acreditar que os fechamentos sejam um hack de design complexo para resolver problemas específicos.Não, os encerramentos são apenas sobre o uso de uma variável que vem de um escopo superior da perspectiva de onde a função foi declarada (não executada).

Agora o que é permite o que você faz pode ser mais espetacular, veja outras respostas.

Exemplo para o primeiro ponto de dlaliberte:

Um encerramento não é criado apenas quando você retorna uma função interna.Na verdade, a função envolvente não precisa retornar.Em vez disso, você pode atribuir sua função interna a uma variável em um escopo externo ou passá-la como argumento para outra função onde possa ser usada imediatamente.Portanto, o encerramento da função envolvente provavelmente já existe no momento em que a função envolvente foi chamada, uma vez que qualquer função interna tem acesso a ela assim que é chamada.

var i;
function foo(x) {
    var tmp = 3;
    i = function (y) {
        console.log(x + y + (++tmp));
    }
}
foo(2);
i(3);

Um encerramento é onde uma função interna tem acesso a variáveis ​​em sua função externa.Essa é provavelmente a explicação de uma linha mais simples que você pode obter para encerramentos.

Sei que já existem muitas soluções, mas acho que este script pequeno e simples pode ser útil para demonstrar o conceito:

// makeSequencer will return a "sequencer" function
var makeSequencer = function() {
    var _count = 0; // not accessible outside this function
    var sequencer = function () {
        return _count++;
    }
    return sequencer;
}

var fnext = makeSequencer();
var v0 = fnext();     // v0 = 0;
var v1 = fnext();     // v1 = 1;
var vz = fnext._count // vz = undefined

Você vai dormir em casa e convida Dan.Você diz a Dan para trazer um controlador de XBox.

Dan convida Paul.Dan pede a Paul que traga um controlador.Quantos controladores foram trazidos para a festa?

function sleepOver(howManyControllersToBring) {

    var numberOfDansControllers = howManyControllersToBring;

    return function danInvitedPaul(numberOfPaulsControllers) {
        var totalControllers = numberOfDansControllers + numberOfPaulsControllers;
        return totalControllers;
    }
}

var howManyControllersToBring = 1;

var inviteDan = sleepOver(howManyControllersToBring);

// The only reason Paul was invited is because Dan was invited. 
// So we set Paul's invitation = Dan's invitation.

var danInvitedPaul = inviteDan(howManyControllersToBring);

alert("There were " + danInvitedPaul + " controllers brought to the party.");

As funções JavaScript podem acessar:

  1. Argumentos
  2. Locais (ou seja, suas variáveis ​​locais e funções locais)
  3. Meio Ambiente, que inclui:
    • globais, incluindo o DOM
    • qualquer coisa em funções externas

Se uma função acessa seu ambiente, então a função é um encerramento.

Observe que as funções externas não são obrigatórias, embora ofereçam benefícios que não discuto aqui.Ao acessar dados em seu ambiente, um encerramento mantém esses dados vivos.No subcaso de funções externas/internas, uma função externa pode criar dados locais e eventualmente sair, e ainda assim, se alguma(s) função(ões) interna(s) sobreviver(em) após a saída da função externa, então a(s) função(ões) interna(s) manterá os dados locais da função externa vivo.

Exemplo de encerramento que utiliza o ambiente global:

Imagine que os eventos dos botões Stack Overflow Vote-Up e Vote-Down são implementados como fechamentos, voteUp_click e voteDown_click, que têm acesso às variáveis ​​externas isVotedUp e isVotedDown, que são definidas globalmente.(Para simplificar, estou me referindo aos botões Question Vote do StackOverflow, não ao conjunto de botões Answer Vote.)

Quando o usuário clica no botão VoteUp, a função voteUp_click verifica se isVotedDown == true para determinar se deve votar a favor ou simplesmente cancelar um voto negativo.A função voteUp_click é um encerramento porque está acessando seu ambiente.

var isVotedUp = false;
var isVotedDown = false;

function voteUp_click() {
  if (isVotedUp)
    return;
  else if (isVotedDown)
    SetDownVote(false);
  else
    SetUpVote(true);
}

function voteDown_click() {
  if (isVotedDown)
    return;
  else if (isVotedUp)
    SetUpVote(false);
  else
    SetDownVote(true);
}

function SetUpVote(status) {
  isVotedUp = status;
  // Do some CSS stuff to Vote-Up button
}

function SetDownVote(status) {
  isVotedDown = status;
  // Do some CSS stuff to Vote-Down button
}

Todas essas quatro funções são encerramentos, pois todas acessam seu ambiente.

O autor de Fechamentos explicou os fechamentos muito bem, explicando o motivo pelo qual precisamos deles e também explicando o LexicalEnvironment, que é necessário para entender os fechamentos.
Aqui está o resumo:

E se uma variável for acessada, mas não for local?Como aqui:

Enter image description here

Nesse caso, o intérprete encontra a variável no externo LexicalEnvironment objeto.

O processo consiste em duas etapas:

  1. Primeiro, quando uma função f é criada, ela não é criada em um vazio espaço.Existe um objeto LexicalEnvironment atual.No caso acima, é janela (a está indefinida no momento da função criação).

Enter image description here

Quando uma função é criada, ela obtém uma propriedade oculta, chamada [[Scope]], que faz referência ao LexicalEnvironment atual.

Enter image description here

Se uma variável for lida, mas não puder ser encontrada em lugar nenhum, um erro será gerado.

Funções aninhadas

As funções podem ser aninhadas uma dentro da outra, formando uma cadeia de LexicalEnvironments que também pode ser chamada de cadeia de escopo.

Enter image description here

Portanto, a função g tem acesso a g, a e f.

Fechamentos

Uma função aninhada pode continuar ativa após a conclusão da função externa:

Enter image description here

Marcando LexicalEnvironments:

Enter image description here

Como vemos, this.say é uma propriedade no objeto de usuário, portanto, continua ativa após a conclusão do usuário.

E se você se lembra, quando this.say é criado, ele (como toda função) obtém uma referência interna this.say.[[Scope]] para o LexicalEnvironment atual.Assim, o LexicalEnvironment da execução atual do usuário permanece na memória.Todas as variáveis ​​de User também são suas propriedades, portanto elas também são mantidas com cuidado, e não descartadas como normalmente.

O objetivo é garantir que, se a função interna quiser acessar uma variável externa no futuro, ela seja capaz de fazê-lo.

Para resumir:

  1. A função interna mantém uma referência à externa Ambiente Lexical.
  2. A função interna pode acessar variáveis a partir dela a qualquer momento, mesmo que a função externa esteja concluída.
  3. O navegador mantém o LexicalEnvironment e todas as suas propriedades (variáveis) na memória até que haja uma função interna que o faça referência.

Isso é chamado de fechamento.

Como pai de uma criança de 6 anos, atualmente ensinando crianças pequenas (e relativamente novato em codificação, sem educação formal, portanto, serão necessárias correções), acho que a lição seria melhor mantida por meio de brincadeiras práticas.Se a criança de 6 anos estiver pronta para entender o que é um fechamento, então ela mesma terá idade suficiente para tentar.Eu sugiro colar o código em jsfiddle.net, explicar um pouco e deixá-los sozinhos para inventar uma música única.O texto explicativo abaixo é provavelmente mais apropriado para uma criança de 10 anos.

function sing(person) {

    var firstPart = "There was " + person + " who swallowed ";

    var fly = function() {
        var creature = "a fly";
        var result = "Perhaps she'll die";
        alert(firstPart + creature + "\n" + result);
    };

    var spider = function() {
        var creature = "a spider";
        var result = "that wiggled and jiggled and tickled inside her";
        alert(firstPart + creature + "\n" + result);
    };

    var bird = function() {
        var creature = "a bird";
        var result = "How absurd!";
        alert(firstPart + creature + "\n" + result);
    };

    var cat = function() {
        var creature = "a cat";
        var result = "Imagine That!";
        alert(firstPart + creature + "\n" + result);
    };

    fly();
    spider();
    bird();
    cat();
}

var person="an old lady";

sing(person);

INSTRUÇÕES

DADOS:Os dados são uma coleção de fatos.Podem ser números, palavras, medidas, observações ou até mesmo descrições de coisas.Você não pode tocá-lo, cheirá-lo ou saboreá-lo.Você pode escrever, falar e ouvir.Você poderia usá-lo para criar toque, olfato e paladar usando um computador.Pode ser útil por meio de um computador usando código.

CÓDIGO:Toda a escrita acima é chamada código.Está escrito em JavaScript.

JAVASCRIPT:JavaScript é uma linguagem.Assim como o inglês, o francês ou o chinês são línguas.Existem muitas linguagens que são compreendidas por computadores e outros processadores eletrônicos.Para que o JavaScript seja compreendido por um computador, ele precisa de um intérprete.Imagine se um professor que só fala russo viesse dar sua aula na escola.Quando o professor dissesse "все садятся", a turma não entenderia.Mas, felizmente, você tem um aluno russo em sua turma que diz a todos que isso significa “todos se sentam” – e é o que todos fazem.A aula é como um computador e o aluno russo é o intérprete.Para JavaScript, o interpretador mais comum é chamado de navegador.

NAVEGADOR:Ao se conectar à Internet em um computador, tablet ou telefone para visitar um site, você usa um navegador.Exemplos que você deve conhecer são Internet Explorer, Chrome, Firefox e Safari.O navegador pode entender JavaScript e informar ao computador o que ele precisa fazer.As instruções JavaScript são chamadas de funções.

FUNÇÃO:Uma função em JavaScript é como uma fábrica.Pode ser uma pequena fábrica com apenas uma máquina dentro.Ou pode conter muitas outras pequenas fábricas, cada uma com muitas máquinas realizando trabalhos diferentes.Em uma fábrica de roupas da vida real, você pode ter resmas de tecido e bobinas de linha entrando e camisetas e jeans saindo.Nossa fábrica JavaScript processa apenas dados, não pode costurar, fazer furos ou derreter metal.Em nossa fábrica de JavaScript, os dados entram e saem.

Toda essa coisa de dados parece um pouco chata, mas é realmente muito legal;poderíamos ter uma função que diga a um robô o que fazer para o jantar.Digamos que eu convide você e seu amigo para minha casa.Você gosta mais de coxinha de frango, eu gosto de linguiça, seu amigo sempre quer o que você quer e meu amigo não come carne.

Não tenho tempo para fazer compras, então a função precisa saber o que temos na geladeira para tomar decisões.Cada ingrediente tem um tempo de cozimento diferente e queremos que tudo seja servido quente pelo robô ao mesmo tempo.Precisamos fornecer à função os dados sobre o que gostamos, a função poderia 'falar' com a geladeira e a função poderia controlar o robô.

Uma função normalmente possui um nome, parênteses e colchetes.Assim:

function cookMeal() {  /*  STUFF INSIDE THE FUNCTION  */  }

Observe que /*...*/ e // impedir que o código seja lido pelo navegador.

NOME:Você pode chamar uma função com qualquer palavra que desejar.O exemplo "cookMeal" é típico de juntar duas palavras e dar à segunda uma letra maiúscula no início - mas isso não é necessário.Não pode ter um espaço e não pode ser um número por si só.

PARÊNTESES:"Parênteses" ou () são a caixa de correio na porta da fábrica de funções JavaScript ou uma caixa de correio na rua para envio de pacotes de informações para a fábrica.Às vezes, a caixa postal pode estar marcada por exemplo cookMeal(you, me, yourFriend, myFriend, fridge, dinnerTime), nesse caso você sabe quais dados deve fornecer.

APARELHO ORTODÔNTICO:"Aparelhos" que se parecem com isto {} são os vidros fumados da nossa fábrica.De dentro da fábrica você consegue ver o exterior, mas de fora você não consegue ver o interior.

O LONGO EXEMPLO DE CÓDIGO ACIMA

Nosso código começa com a palavra função, então sabemos que é um!Então o nome da função cantar - essa é minha própria descrição do que trata a função.Então parênteses ().Os parênteses estão sempre presentes para uma função.Às vezes eles estão vazios e às vezes eles têm algo dentro.Este tem uma palavra em: (person).Depois disso, há uma chave como esta { .Isso marca o início da função cantar().Tem um parceiro que marca o fim da cantar() assim }

function sing(person) {  /* STUFF INSIDE THE FUNCTION */  }

Portanto, esta função pode ter algo a ver com canto e pode precisar de alguns dados sobre uma pessoa.Ele contém instruções para fazer algo com esses dados.

Agora, depois da função cantar(), perto do final do código está a linha

var person="an old lady";

VARIÁVEL:As cartas var significa "variável".Uma variável é como um envelope.Do lado de fora deste envelope está marcado “pessoa”.No interior contém um pedaço de papel com as informações que nossa função necessita, algumas letras e espaços unidos como um pedaço de barbante (chama-se barbante) que formam uma frase que diz "uma velhinha".Nosso envelope poderia conter outros tipos de coisas como números (chamados inteiros), instruções (chamadas funções), listas (chamadas matrizes).Porque esta variável é escrita fora de todas as chaves {}, e como você pode ver através das janelas escuras quando está dentro dos colchetes, essa variável pode ser vista em qualquer lugar do código.Chamamos isso de 'variável global'.

VARIÁVEL GLOBAL: pessoa é uma variável global, o que significa que se você alterar seu valor de “uma senhora idosa” para “um jovem”, o pessoa continuará sendo um jovem até que você decida alterá-lo novamente e que qualquer outra função no código possa ver que é um jovem.aperte o F12 ou consulte as configurações de Opções para abrir o console do desenvolvedor de um navegador e digite "pessoa" para ver qual é esse valor.Tipo person="a young man" para alterá-lo e digite "pessoa" novamente para ver se ele mudou.

Depois disso temos a linha

sing(person);

Esta linha está chamando a função, como se estivesse chamando um cachorro

"Vamos cantar, Venha e pegue pessoa!"

Quando o navegador carregar o código JavaScript e chegar a esta linha, ele iniciará a função.Coloquei a linha no final para garantir que o navegador tenha todas as informações necessárias para executá-lo.

As funções definem ações - a função principal é cantar.Ele contém uma variável chamada primeira parte que se aplica ao canto sobre a pessoa que se aplica a cada um dos versos da música:“Tinha “+ pessoa +” que engoliu”.Se você digitar primeira parte no console, você não obterá uma resposta porque a variável está bloqueada em uma função - o navegador não pode ver dentro das janelas escuras dos colchetes.

ENCERRAMENTOS:Os encerramentos são as funções menores que estão dentro do grande cantar() função.As pequenas fábricas dentro da grande fábrica.Cada um deles tem seus próprios colchetes, o que significa que as variáveis ​​dentro deles não podem ser vistas de fora.É por isso que os nomes das variáveis ​​(criatura e resultado) pode ser repetido nos fechamentos, mas com valores diferentes.Se você digitar esses nomes de variáveis ​​na janela do console, não obterá seu valor porque está oculto por duas camadas de janelas coloridas.

Todos os fechamentos sabem o que cantar() variável da função chamada primeira parte é porque eles podem ver através de suas janelas escuras.

Depois dos fechamentos vêm as filas

fly();
spider();
bird();
cat();

A função sing() chamará cada uma dessas funções na ordem em que são fornecidas.Então o trabalho da função sing() estará concluído.

Ok, conversando com uma criança de 6 anos, eu possivelmente usaria as seguintes associações.

Imagine - você está brincando com seus irmãos e irmãs mais novos em toda a casa, e você está se movimentando com seus brinquedos e trouxe alguns deles para o quarto do seu irmão mais velho.Depois de um tempo seu irmão voltou da escola e foi para o quarto dele, e ele se trancou dentro dele, então agora você não conseguia mais acessar os brinquedos deixados ali de forma direta.Mas você poderia bater na porta e pedir aqueles brinquedos ao seu irmão.Isso é chamado de brinquedo fecho;seu irmão inventou isso para você, e agora ele está no exterior escopo.

Compare com uma situação em que uma porta foi trancada por corrente de ar e não havia ninguém dentro (execução de função geral), e então algum incêndio local ocorreu e queimou a sala (coletor de lixo:D), e então uma nova sala foi construída e agora você pode sair outros brinquedos lá (nova instância de função), mas nunca receba os mesmos brinquedos que foram deixados na primeira instância da sala.

Para uma criança avançada eu colocaria algo como o seguinte.Não é perfeito, mas faz você sentir o que é:

function playingInBrothersRoom (withToys) {
  // We closure toys which we played in the brother's room. When he come back and lock the door
  // your brother is supposed to be into the outer [[scope]] object now. Thanks god you could communicate with him.
  var closureToys = withToys || [],
      returnToy, countIt, toy; // Just another closure helpers, for brother's inner use.

  var brotherGivesToyBack = function (toy) {
    // New request. There is not yet closureToys on brother's hand yet. Give him a time.
    returnToy = null;
    if (toy && closureToys.length > 0) { // If we ask for a specific toy, the brother is going to search for it.

      for ( countIt = closureToys.length; countIt; countIt--) {
        if (closureToys[countIt - 1] == toy) {
          returnToy = 'Take your ' + closureToys.splice(countIt - 1, 1) + ', little boy!';
          break;
        }
      }
      returnToy = returnToy || 'Hey, I could not find any ' + toy + ' here. Look for it in another room.';
    }
    else if (closureToys.length > 0) { // Otherwise, just give back everything he has in the room.
      returnToy = 'Behold! ' + closureToys.join(', ') + '.';
      closureToys = [];
    }
    else {
      returnToy = 'Hey, lil shrimp, I gave you everything!';
    }
    console.log(returnToy);
  }
  return brotherGivesToyBack;
}
// You are playing in the house, including the brother's room.
var toys = ['teddybear', 'car', 'jumpingrope'],
    askBrotherForClosuredToy = playingInBrothersRoom(toys);

// The door is locked, and the brother came from the school. You could not cheat and take it out directly.
console.log(askBrotherForClosuredToy.closureToys); // Undefined

// But you could ask your brother politely, to give it back.
askBrotherForClosuredToy('teddybear'); // Hooray, here it is, teddybear
askBrotherForClosuredToy('ball'); // The brother would not be able to find it.
askBrotherForClosuredToy(); // The brother gives you all the rest
askBrotherForClosuredToy(); // Nothing left in there

Como você pode ver, os brinquedos deixados no quarto ainda estão acessíveis através do irmão e não importa se o quarto está trancado.Aqui está um jsbin para brincar com isso.

Uma resposta para uma criança de seis anos (supondo que ela saiba o que é uma função, o que é uma variável e o que são dados):

Funções podem retornar dados.Um tipo de dado que você pode retornar de uma função é outra função.Quando essa nova função é retornada, todas as variáveis ​​e argumentos usados ​​na função que a criou não desaparecem.Em vez disso, essa função pai "fecha". Em outras palavras, nada pode olhar para dentro dele e ver as variáveis que ele usou, exceto a função que ele retornou.Essa nova função tem uma capacidade especial de olhar para dentro da função que a criou e ver os dados dentro dela.

function the_closure() {
  var x = 4;
  return function () {
    return x; // Here, we look back inside the_closure for the value of x
  }
}

var myFn = the_closure();
myFn(); //=> 4

Outra maneira realmente simples de explicar isso é em termos de escopo:

Sempre que você criar um osciloscópio menor dentro de um osciloscópio maior, o osciloscópio menor sempre será capaz de ver o que está no escopo maior.

Uma função em JavaScript não é apenas uma referência a um conjunto de instruções (como na linguagem C), mas também inclui uma estrutura de dados oculta que é composta de referências a todas as variáveis ​​não locais que utiliza (variáveis ​​capturadas).Essas funções de duas peças são chamadas de fechamentos.Cada função em JavaScript pode ser considerada um encerramento.

Closures são funções com um estado.É um pouco semelhante a "this" no sentido de que "this" também fornece estado para uma função, mas function e "this" são objetos separados ("this" é apenas um parâmetro sofisticado e a única maneira de vinculá-lo permanentemente a um função é criar um encerramento).Embora "this" e função sempre vivam separadamente, uma função não pode ser separada de seu fechamento e a linguagem não fornece meios de acessar variáveis ​​capturadas.

Como todas essas variáveis ​​externas referenciadas por uma função aninhada lexicamente são, na verdade, variáveis ​​locais na cadeia de suas funções lexicalmente delimitadas (as variáveis ​​globais podem ser consideradas variáveis ​​locais de alguma função raiz), e cada execução de uma função cria novas instâncias de suas variáveis ​​locais, segue-se que cada execução de uma função que retorna (ou de outra forma a transfere, como registrá-la como um retorno de chamada) uma função aninhada cria um novo encerramento (com seu próprio conjunto potencialmente único de variáveis ​​não locais referenciadas que representam sua execução contexto).

Além disso, deve ser entendido que variáveis ​​locais em JavaScript não são criadas no stack frame, mas no heap e destruídas somente quando ninguém as faz referência.Quando uma função retorna, as referências às suas variáveis ​​locais são decrementadas, mas ainda podem ser não nulas se durante a execução atual elas se tornarem parte de um fechamento e ainda forem referenciadas por suas funções aninhadas lexicamente (o que só pode acontecer se as referências a essas funções aninhadas foram retornadas ou transferidas para algum código externo).

Um exemplo:

function foo (initValue) {
   //This variable is not destroyed when the foo function exits.
   //It is 'captured' by the two nested functions returned below.
   var value = initValue;

   //Note that the two returned functions are created right now.
   //If the foo function is called again, it will return
   //new functions referencing a different 'value' variable.
   return {
       getValue: function () { return value; },
       setValue: function (newValue) { value = newValue; }
   }
}

function bar () {
    //foo sets its local variable 'value' to 5 and returns an object with
    //two functions still referencing that local variable
    var obj = foo(5);

    //Extracting functions just to show that no 'this' is involved here
    var getValue = obj.getValue;
    var setValue = obj.setValue;

    alert(getValue()); //Displays 5
    setValue(10);
    alert(getValue()); //Displays 10

    //At this point getValue and setValue functions are destroyed
    //(in reality they are destroyed at the next iteration of the garbage collector).
    //The local variable 'value' in the foo is no longer referenced by
    //anything and is destroyed too.
}

bar();

Talvez um pouco além de tudo, exceto o mais precoce das crianças de seis anos, mas alguns exemplos que ajudaram a fazer o conceito de encerramento em JavaScript clicar para mim.

Um encerramento é uma função que tem acesso ao escopo de outra função (suas variáveis ​​e funções).A maneira mais fácil de criar um encerramento é com uma função dentro de uma função;a razão é que em JavaScript uma função sempre tem acesso ao escopo da função que a contém.

function outerFunction() {
    var outerVar = "monkey";
    
    function innerFunction() {
        alert(outerVar);
    }
    
    innerFunction();
}

outerFunction();

ALERTA:macaco

No exemplo acima, outerFunction é chamado, que por sua vez chama innerFunction.Observe como outerVar está disponível para innerFunction, evidenciado por alertar corretamente o valor de outerVar.

Agora considere o seguinte:

function outerFunction() {
    var outerVar = "monkey";
    
    function innerFunction() {
        return outerVar;
    }
    
    return innerFunction;
}

var referenceToInnerFunction = outerFunction();
alert(referenceToInnerFunction());

ALERTA:macaco

referenceToInnerFunction é definido como outerFunction(), que simplesmente retorna uma referência a innerFunction.Quando referenceToInnerFunction é chamado, ele retorna outerVar.Novamente, como acima, isso demonstra que innerFunction tem acesso a outerVar, uma variável de outerFunction.Além disso, é interessante notar que ele mantém esse acesso mesmo após a execução de outerFunction terminar.

E é aqui que as coisas ficam realmente interessantes.Se nos livrarmos de outerFunction, digamos, defini-lo como nulo, você pode pensar que referenceToInnerFunction perderia seu acesso ao valor de outerVar.Mas este não é o caso.

function outerFunction() {
    var outerVar = "monkey";
    
    function innerFunction() {
        return outerVar;
    }
    
    return innerFunction;
}

var referenceToInnerFunction = outerFunction();
alert(referenceToInnerFunction());

outerFunction = null;
alert(referenceToInnerFunction());

ALERTA:macaco ALERTA:macaco

Mas como é isso?Como referenceToInnerFunction ainda pode saber o valor de outerVar agora que outerFunction foi definido como nulo?

A razão pela qual referenceToInnerFunction ainda pode acessar o valor de outerVar é porque quando o fechamento foi criado pela primeira vez colocando innerFunction dentro de outerFunction, innerFunction adicionou uma referência ao escopo de outerFunction (suas variáveis ​​e funções) à sua cadeia de escopo.O que isso significa é que innerFunction possui um ponteiro ou referência para todas as variáveis ​​de outerFunction, incluindo outerVar.Portanto, mesmo quando a execução de outerFunction terminar, ou mesmo se for excluída ou definida como nula, as variáveis ​​em seu escopo, como outerVar, permanecerão na memória por causa da excelente referência a elas por parte de innerFunction que foi retornada para referenceToInnerFunction.Para realmente liberar outerVar e o restante das variáveis ​​de outerFunction da memória, você teria que se livrar dessa excelente referência a elas, digamos, definindo referenceToInnerFunction como null também.

//////////

Duas outras coisas a serem observadas sobre fechamentos.Primeiro, o encerramento sempre terá acesso aos últimos valores da função que o contém.

function outerFunction() {
    var outerVar = "monkey";
    
    function innerFunction() {
        alert(outerVar);
    }
    
    outerVar = "gorilla";

    innerFunction();
}

outerFunction();

ALERTA:gorila

Em segundo lugar, quando um fecho é criado, ele retém uma referência a todas as variáveis ​​e funções da sua função envolvente;não é possível escolher.E mesmo assim, os encerramentos devem ser usados ​​com moderação, ou pelo menos com cuidado, pois podem consumir muita memória;muitas variáveis ​​​​podem ser mantidas na memória muito depois de a função que a contém terminar de ser executada.

Eu simplesmente apontaria para eles o Página de fechamentos da Mozilla.É o melhor, mais explicação concisa e simples de noções básicas de fechamento e uso prático que encontrei.É altamente recomendado para quem está aprendendo JavaScript.

E sim, eu até recomendaria isso para uma criança de 6 anos - se a criança de 6 anos está aprendendo sobre encerramentos, então é lógico que ela esteja pronta para compreender o explicação concisa e simples fornecido no artigo.

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top