function tradução - Como funcionam os fechamentos de JavaScript?




closures what (25)

Como você explicaria os encerramentos de JavaScript para alguém com conhecimento dos conceitos que eles consistem (por exemplo, funções, variáveis ​​e afins), mas não entende os encerramentos em si?

Eu vi o exemplo Scheme dado na Wikipedia, mas infelizmente isso não ajudou.


Answers

Funções JavaScript podem acessar suas:

  1. Argumentos
  2. Locais (isto é, suas variáveis ​​locais e funções locais)
  3. Ambiente, que inclui:
    • globals, incluindo o DOM
    • qualquer coisa nas funções externas

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

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

Exemplo de um encerramento que usa o ambiente global:

Imagine que os eventos de botão Voto para cima e Votar para baixo do Stack Overflow sejam implementados como closures, voteUp_click e voteDown_click, que têm acesso a variáveis ​​externas isVotedUp e isVotedDown, que são definidos globalmente. (Para simplificar, estou me referindo aos botões Question Vote do StackOverflow, não ao array 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 ou simplesmente cancelar um voto para baixo. 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.


Eu simplesmente os apontaria para a página do Mozilla Closures . É a melhor, mais concisa e simples explicação sobre o básico do fechamento e o uso prático que encontrei. É altamente recomendado para qualquer pessoa que aprenda JavaScript.

E sim, eu até o recomendaria para uma criança de 6 anos - se a criança de 6 anos estiver aprendendo sobre fechamentos, é lógico que eles estão prontos para compreender a explicação concisa e simples fornecida no artigo.


Eu tendo a aprender melhor por comparações BOM / BAD. Eu gosto de ver código de trabalho seguido por código não funcional que alguém provavelmente encontrará. Eu coloquei um jsFiddle que faz uma comparação e tenta reduzir as diferenças para as explicações mais simples que eu poderia criar.

Fechamentos feitos direito:

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 npara destacar que é uma nova variável criada em um novo escopo de função e não é a mesma variável indexque está vinculada ao escopo externo.

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

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

  • Dentro de cada escopo nestá vinculado a qualquer valor que tenha quando createClosure(n)foi invocado, portanto, a função aninhada que é retornada sempre retornará o valor de nquando ela createClosure(n)foi invocada.

Fechamentos feitos erradamente:

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 da createClosureArray()função e a função agora retorna a matriz concluída, o que à primeira vista parece mais intuitivo.

  • O que pode não ser óbvio é que since createClosureArray()é chamado apenas uma vez que apenas um escopo é criado para essa função, em vez de um para cada iteração do loop.

  • Dentro dessa função, uma variável nomeada indexé definida. O loop é executado e adiciona funções à matriz que retorna index. Note que indexé definido dentro da createClosureArrayfunção que só é invocada uma vez.

  • Porque havia apenas um escopo dentro da createClosureArray()função, indexsó é vinculado a um valor dentro desse escopo. Em outras palavras, cada vez que o loop altera o valor de index, ele o altera para tudo que faz referência a ele dentro desse escopo.

  • Todas as funções adicionadas à matriz retornam a indexvariável SAME do escopo pai onde ela foi definida, em vez de 10 diferentes de 10 escopos diferentes, como o 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 indexterminar de ser modificado, o valor final era 10, portanto, cada função adicionada à matriz retorna o valor da única indexvariável que agora está definida como 10.

Resultado

CLASSES FEITAS À DIREITA
n = 0
n = 1
n = 2
n = 3
n = 4
n = 5
n = 6
n = 7
n = 8
n = 9

CLASSES FEITAS ERRADAS
n = 10
n = 10
n = 10
n = 10
n = 10
n = 10
n = 10
n = 10
n = 10
n = 10


Exemplo para o primeiro ponto por dlaliberte:

Um fechamento não é criado apenas quando você retorna uma função interna. Na verdade, a função de inclusão não precisa retornar. Você pode, em vez disso, atribuir sua função interna a uma variável em um escopo externo ou passá-la como um argumento para outra função em que ela poderia ser usada imediatamente. Portanto, o fechamento da função de fechamento provavelmente já existe no momento em que a função de fechamento foi chamada, já 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);

Uma resposta para uma pessoa de seis anos de idade (supondo que ele saiba o que é uma função e o que é uma variável e quais dados são):

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 dentro dela e ver as variáveis ​​usadas, exceto a função retornada. 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 muito simples de explicar isso é em termos de escopo:

Sempre que você criar um escopo menor dentro de um escopo maior, o escopo menor sempre poderá ver o que está no escopo maior.


PREFÁCIO: esta resposta foi escrita quando a questão era:

Como o velho Albert disse: "Se você não pode explicar isso para uma criança de seis anos, você realmente não entende isso." Bem, eu tentei explicar o fechamento de JS a um amigo de 27 anos e falhei completamente.

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

Tenho certeza de que fui uma das únicas pessoas que tentaram tirar a questão inicial literalmente. Desde então, a questão sofreu uma mutação várias vezes, então minha resposta pode parecer incrivelmente estúpida e fora de lugar. Espero que a ideia geral da história seja divertida para alguns.

Eu sou um grande fã de analogia e metáfora ao explicar conceitos difíceis, então deixe-me tentar minha mão com uma história.

Era uma vez:

Houve uma princesa ...

function princess() {

Ela vivia em um mundo maravilhoso cheio de aventuras. Ela conheceu seu príncipe encantado, rodeou seu mundo em um unicórnio, lutou com 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 tedioso de tarefas e adultos.

    return {

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

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

Mas tudo o que eles veriam é uma menininha ...

var littleGirl = princess();

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

littleGirl.story();

E mesmo que os adultos soubessem de princesas reais, eles nunca acreditariam nos unicórnios ou dragões porque nunca poderiam vê-los. Os adultos disseram que eles só existiam dentro da imaginação da menina.

Mas nós sabemos a verdade real; que a garotinha com a princesa dentro ...

... é realmente uma princesa com uma menina dentro.


Eu escrevi um post no blog um tempo atrás explicando encerramentos. Aqui está o que eu disse sobre encerramentos em termos de por que você iria querer um.

Fechamentos são uma maneira de permitir que uma função tenha variáveis ​​privadas persistentes - isto é, variáveis ​​que apenas uma função conhece, onde ela pode acompanhar as informações de tempos anteriores que foram executadas.

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

Postagem completa:

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


Sempre que você vir a palavra-chave da função dentro de outra função, a função interna terá acesso a 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);

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

Isso é um encerramento. Uma função não precisa retornar para ser chamada 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 a bar ainda pode se referir a x e tmp , mesmo que não esteja mais diretamente dentro do escopo.

No entanto, como o tmp ainda está por dentro do fechamento da bar , ele também está sendo incrementado. Ele será incrementado toda vez que você ligar para a bar .

O exemplo mais simples de fechamento é 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 de função e o objeto pai, esse 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 fechamento, retornando uma lista delas ou configurando-as para variáveis ​​globais. Todos estes se referem ao mesmo x ao mesmo tmp , eles não fazem suas próprias cópias.

Aqui o número x é um número literal. Como 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 disser, você chamou foo com um objeto, o fechamento retornará fará referência ao 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 pode não ser esperado, é que x está simplesmente se referindo ao mesmo objeto que a variável age ! Depois de um par de chamadas para bar , age.memb será 2! Essa referência é a base para vazamentos de memória com objetos HTML.


Como pai de uma criança de seis anos de idade, atualmente ensinando crianças pequenas (e um relativamente novato à codificação sem educação formal, então as correções serão necessárias), acho que a lição seria melhor através de brincadeiras práticas. Se a criança de 6 anos está pronta para entender o que é um fechamento, então eles são velhos o suficiente para ter uma chance. Sugiro colar o código em jsfiddle.net, explicando um pouco e deixando-os sozinhos para criar 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. Pode ser números, palavras, medidas, observações ou apenas descrições de coisas. Você não pode tocá-lo, cheirá-lo ou prová-lo. Você pode escrever, falar e ouvir. Você pode usá-lo para criar um toque de cheiro e sabor usando um computador. Pode ser útil por um computador usando código.

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

JAVASCRIPT: JavaScript é uma linguagem. Como inglês ou francês ou chinês são idiomas. Existem muitas linguagens que são entendidas por computadores e outros processadores eletrônicos. Para que o JavaScript seja entendido por um computador, ele precisa de um intérprete. Imagine se um professor que só fala russo venha ensinar sua turma na escola. Quando o professor disser "все садятся", a turma não entenderá. Mas, felizmente, você tem um aluno russo em sua classe que diz a todos que isso significa "todo mundo senta" - então todos vocês o fazem. A aula é como um computador e o aluno russo é o intérprete. Para JavaScript, o intérprete mais comum é chamado de navegador.

NAVEGADOR: Quando você se conecta à Internet em um computador, tablet ou telefone para visitar um site, você usa um navegador. Exemplos que você pode saber são o Internet Explorer, Chrome, Firefox e Safari. O navegador pode entender o JavaScript e informar ao computador o que ele precisa fazer. As instruções do 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 fazendo trabalhos diferentes. Em uma fábrica de roupas da vida real, você pode ter pilhas de tecidos e bobinas de fios entrando e camisetas e jeans saindo. Nossa fábrica de JavaScript só processa dados, não pode costurar, perfurar ou derreter metal. Em nossos dados de fábrica JavaScript entra e os dados saem.

Todo esse material de dados soa um pouco chato, mas é realmente muito legal; podemos ter uma função que diz ao robô o que fazer para o jantar. Vamos dizer que convido você e seu amigo para a minha casa. Você gosta mais de pernas de frango, eu gosto de salsichas, seu amigo sempre quer o que você quer e meu amigo não come carne.

Eu 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 tem um nome, parênteses e chaves. Como isso:

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

Observe isso /*...*/e //pare o código sendo lido pelo navegador.

NAME: Você pode chamar uma função praticamente qualquer palavra que quiser. O exemplo "cookMeal" é típico em unir duas palavras e dar ao segundo 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 conta própria.

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

BRACES: "Aparelhos" que se parecem com isso {}são as janelas coloridas da nossa fábrica. De dentro da fábrica você pode ver, mas do lado de fora você não pode ver.

O EXEMPLO DE CÓDIGO LONGO ACIMA

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

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

Então, essa função pode ter algo a ver com canto e pode precisar de alguns dados sobre uma pessoa. Tem instruções internas para fazer algo com esses dados.

Agora, depois da função sing () , perto do final do código é a linha

var person="an old lady";

VARIÁVEL: As letras var significam "variável". Uma variável é como um envelope. Do lado de fora, este envelope está marcado como "pessoa". No lado de dentro, ele contém um pedaço de papel com a informação que nossa função precisa, algumas letras e espaços unidos como um pedaço de corda (é chamado de corda) que fazem uma frase que diz "uma velha senhora". Nosso envelope pode conter outros tipos de coisas, como números (chamados inteiros), instruções (chamadas funções), listas (chamadas matrizes ). Como esta variável é escrita fora de todas as chaves {}, e porque você pode ver através das janelas coloridas quando você está dentro das chaves, essa variável pode ser vista de qualquer lugar no código. Nós chamamos isso de 'variável global'.

VARIÁVEL GLOBAL: a pessoa é uma variável global, significando que se você mudar seu valor de "uma velha senhora" para "um jovem", a pessoa continuará sendo jovem até que você decida mudá-la novamente e que qualquer outra função o código pode ver que é um jovem. Pressione o F12botão ou examine as configurações de Opções para abrir o console do desenvolvedor de um navegador e digite "pessoa" para ver qual é esse valor. Digite person="a young man"para alterá-lo e digite "person" novamente para ver se ele foi alterado.

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 tiver carregado o código JavaScript atingido nessa linha, ele iniciará a função. Coloco a linha no final para garantir que o navegador tenha todas as informações necessárias para executá-lo.

Funções definem ações - a função principal é sobre cantar. Ele contém uma variável chamada firstPart que se aplica ao canto sobre a pessoa que se aplica a cada um dos versos da música: "Houve" + pessoa + "quem engoliu". Se você digitar firstPart no console, não receberá uma resposta porque a variável está bloqueada em uma função - o navegador não pode ver dentro das janelas coloridas das chaves.

CLOSURES: Os fechamentos são as funções menores que estão dentro da função big sing () . As pequenas fábricas dentro da grande fábrica. Cada um tem seu próprio aparelho, 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 ) podem ser repetidos 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 qual é a variável da função sing () chamada firstPart , porque eles podem ver a partir de suas janelas coloridas.

Depois dos fechamentos, vêm as linhas

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

A função sing () chamará cada uma dessas funções na ordem em que elas são dadas. Então o trabalho da função sing () será feito.


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

O escopo de um encerramento em JavaScript é léxico, o que significa que tudo o que está contido na função à qual o fechamento pertence, tem acesso a qualquer variável que esteja nele.

Uma variável está contida no fechamento 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) acessar essa variável sem defini-la em seu próprio escopo com var, ela modifica o conteúdo da variável no fechamento externo .

Um encerramento sobrevive ao tempo de execução da função que o gerou. Se outras funções saírem do fechamento / escopo no qual elas estão definidas (por exemplo, como valores de retorno), elas continuarão a referenciar esse encerramento .

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

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 ele usa (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.

Fechamentos 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 fechamento). Enquanto "this" e função sempre vivem separadamente, uma função não pode ser separada de seu fechamento e a linguagem não fornece meios para acessar variáveis ​​capturadas.

Como todas essas variáveis ​​externas referenciadas por uma função lexicalmente aninhada são, na verdade, variáveis ​​locais na cadeia de suas funções lexicamente delimitadoras (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 Em suas variáveis ​​locais, toda execução de uma função retornando (ou transferindo-a de outra maneira, como registrá-la como um retorno de chamada) uma função aninhada cria um novo fechamento (com seu próprio conjunto 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 são criadas não no quadro da pilha, mas no heap e destruídas somente quando ninguém as está referenciando. Quando uma função retorna, referências a suas variáveis ​​locais são decrementadas, mas elas ainda podem ser não nulas se durante a execução atual elas se tornarem parte de um encerramento e ainda forem referenciadas por suas funções aninhadas lexicalmente (o que pode acontecer somente 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();

Fechamentos são difíceis de explicar porque eles são usados ​​para fazer algum trabalho de comportamento que todo mundo intuitivamente espera que funcione de qualquer maneira. Acho que a melhor maneira de explicá-los (e o modo como 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 o JavaScript não conhecesse closures? Basta substituir a chamada na última linha pelo seu corpo de método (que é basicamente o que as chamadas de função fazem) e você obtém:

console.log(x + 3);

Agora, onde está a definição de x ? Nós não definimos no escopo atual. A única solução é deixar o plus5 carregar seu escopo (ou melhor, o escopo de seus pais) por aí. Desta forma, x é bem definido e está ligado ao valor 5.


Eu montei um tutorial interativo em JavaScript para explicar como o fechamento funciona. O que é um encerramento?

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

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

  • Um fechamento não é criado apenas quando você retorna uma função interna.De fato, a função delimitadora não precisa retornar de forma alguma para que seu fechamento seja criado. Você pode, em vez disso, atribuir sua função interna a uma variável em um escopo externo ou passá-la como um argumento para outra função em que ela possa ser chamada imediatamente ou em qualquer momento posterior. Portanto, o fechamento da função delimitadora é provavelmente criado assim que a função delimitadora é chamada, uma vez que qualquer função interna tem acesso a esse fechamento sempre que a função interna é chamada, antes ou depois que a função delimitadora retorna.
  • Um encerramento não faz referência a uma cópia dos valores antigos de variáveis ​​em seu escopo. As variáveis ​​em si fazem parte do fechamento e, portanto, o valor visto ao acessar uma dessas variáveis ​​é o valor mais recente no momento em que é acessado. É 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 pegar uma cópia das variáveis ​​no momento em que a função é criada ou chamada.
  • As "variáveis" em um fechamento incluem quaisquer funções nomeadas declaradas dentro da função. Eles também incluem argumentos da função. Um fechamento também tem acesso às suas variáveis ​​de fechamento, até o escopo global.
  • Fechamentos usam memória, mas eles 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. Os vazamentos de memória do Internet Explorer que envolvem closures são criados quando ele não desconecta os valores do atributo DOM que fazem referência a closures, mantendo assim referências a estruturas possivelmente circulares.

Você está tendo um sono e convida o Dan. Você diz a Dan para trazer um controlador XBox.

Dan convida Paul. Dan pede a Paul para trazer 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.");

Eu não entendo porque 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, encerramentos são apenas sobre o uso de uma variável que vem de um escopo mais alto da perspectiva de onde a função foi declarada (não executada) .

Agora o que isso permite que você faça pode ser mais espetacular, veja outras respostas.


O autor de Closures explicou muito bem os encerramentos, explicando o motivo pelo qual precisamos deles e também explicando o ambiente lexical necessário para entender os encerramentos.
Aqui está o resumo:

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

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

O processo consiste em duas etapas:

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

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

Se uma variável é lida, mas não pode ser encontrada em nenhum lugar, um erro é 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.

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

Fechamentos

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

Marcando o LexicalEnvironments:

Como vemos, this.sayé uma propriedade no objeto de usuário, portanto, continua a viver após o usuário ter concluído.

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

O ponto principal é 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 ao LexicalEnvironment externo.
  2. A função interna pode acessar variáveis ​​a qualquer momento, mesmo que a função externa esteja terminada.
  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 faça referência a ele.

Isso é chamado de fechamento.


Eu sei que já existem muitas soluções, mas acho que esse 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

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

Em Childhood Development: 5 to 7 Years diz:

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

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

A cozinha é um fechamento que possui uma variável local, chamada trashBags . Há 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

Mais pontos que explicam porque os fechamentos são interessantes:

  • Cada vez que makeKitchen() é chamado, um novo fechamento é criado com seus próprios trashBags separados.
  • A variável trashBags é local para o interior de cada cozinha e não é acessível por fora, mas a função interna da propriedade getTrashBag tem acesso a ela.
  • Cada chamada de função cria um fechamento, mas não haveria necessidade de manter o fechamento a menos que uma função interna, que tenha acesso ao interior do fechamento, possa ser chamada de fora do fechamento. Retornar o objeto com a função getTrashBag faz isso aqui.

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


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

Você sabe como os adultos podem ter uma casa e eles a chamam de lar? Quando uma mãe tem um filho, a criança realmente não possui nada, certo? Mas seus pais possuem uma casa, então sempre que alguém perguntar à criança "Onde está sua casa?", Ela pode responder "aquela casa!" E apontar para a casa de seus pais. Um "Encerramento" é a capacidade da criança de sempre (mesmo se no exterior) poder dizer que tem uma casa, mesmo que sejam os pais que possuem a casa.


Fechamentos são simples:

O seguinte exemplo simples abrange todos os principais pontos de encerramento de JavaScript. *

Aqui está uma fábrica que produz calculadoras que podem adicionar 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 make_calculatorcria uma nova variável local n, que continua a ser usada por aquela calculadora adde multiplyfunciona muito depois dos make_calculatorretornos.

Se você estiver familiarizado com quadros de pilha, essas calculadoras parecerão estranhas: como elas podem continuar acessando napós as make_calculatordevoluções? A resposta é imaginar que o JavaScript não usa "quadros de pilha", mas usa "quadros de heap", que podem persistir após a chamada de função que os fez retornar.

Funções internas como adde multiply, que acessam variáveis ​​declaradas em uma função externa ** , são chamadas de closures .

Isso é praticamente tudo que existe para encerramentos.


* Por exemplo, ele cobre todos os pontos do artigo "Closures for Dummies" fornecido em outra resposta , exceto o exemplo 6, que simplesmente mostra que as variáveis ​​podem ser usadas antes de serem declaradas, um fato interessante de se saber, mas completamente não relacionadas a encerramentos. Ele também cobre todos os pontos da resposta aceita , exceto os pontos (1) que as funções copiam seus argumentos em variáveis ​​locais (os argumentos da função nomeada) e (2) que os números de cópia criam um novo número, mas copiam uma referência de objeto dá-lhe outra referência ao mesmo objeto. Estes também são bons para saber, mas novamente completamente não relacionados a fechamentos. Também é muito semelhante ao exemplo desta resposta, mas um pouco mais curto e menos abstrato. Não abrange o ponto deesta resposta ou este comentário , que é que o JavaScript torna difícil ligar o atualValor de uma variável de loop em sua função interna: A etapa de "plug-in" só pode ser feita com uma função auxiliar que encerra sua função interna e é invocada em cada iteração de loop. (Estritamente falando, a função interna acessa a cópia da variável auxiliar da função, em vez de ter qualquer coisa plugada.) Novamente, muito útil ao criar closures, mas não parte do que é um encerramento ou como ele funciona. Há uma confusão adicional devido a closures trabalhando diferentemente em linguagens funcionais como ML, onde variáveis ​​são ligadas a valores ao invés de espaço de armazenamento, provendo um fluxo constante de pessoas que entendem closures de certa forma (ou seja, o modo "plugging") simplesmente incorreto para JavaScript, onde as variáveis ​​sempre estão vinculadas ao espaço de armazenamento e nunca aos valores.

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


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 pequenos em toda a casa, e você está se movendo com seus brinquedos e trouxe alguns deles para o quarto do irmão mais velho. Depois de um tempo seu irmão voltou da escola e foi para seu quarto, e ele trancou dentro dele, então agora você não podia mais acessar brinquedos deixados lá de uma maneira direta. Mas você poderia bater a porta e pedir a seu irmão por esses brinquedos. Isso é chamado de fechamento do brinquedo ; seu irmão inventou para você e agora ele está no escopo externo .

Compare com uma situação em que uma porta foi trancada por rascunho e ninguém dentro (execução de função geral), e então algum incêndio local e queima 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 obter 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ê se sentir sobre 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 na sala ainda são acessíveis através do irmão e não importa se a sala está trancada. Aqui está um jsbin para brincar com ele.


Fechamentos de JavaScript para iniciantes

Enviado por Morris em Tue, 2006-02-21 10:19. Comunidade editada desde.

Fechamentos não são mágicos

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

Fechamentos não são difíceis de entender, uma vez que o conceito central está quebrado. No entanto, eles são impossíveis de entender lendo qualquer explicação teórica ou academicamente orientada!

Este artigo é destinado a programadores com alguma experiência em programação em uma linguagem mainstream e que podem 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ída quando a função sai. As variáveis ​​apenas se tornam invisíveis para o mundo exterior. Foo pode, portanto, astutamente retornar a barra de funções 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 fechamento") que ninguém mais pode interferir, nem mesmo alguém que chama foo novamente no futuro.

  • Um encerramento é uma maneira de suportar funções de primeira classe ; é uma expressão que pode referenciar variáveis ​​dentro de seu escopo (quando foi declarada pela primeira vez), ser atribuída a uma variável, ser passada como um argumento para uma função ou ser retornada como um resultado de 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 a uma variável ( say2 ) no código acima. Se você não fizer isso, então você precisa olhar para isso antes de poder aprender o fechamento. Um programador usando C pensaria na função como retornando um ponteiro para uma função, e que as variáveis say e say say2 são, cada uma, 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 tanto um ponteiro para uma função quanto um ponteiro oculto para um fechamento.

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

Em C e na maioria das outras linguagens comuns, depois que uma função retorna, todas as variáveis ​​locais não estão mais acessíveis porque o quadro de pilha é destruído.

Em JavaScript, se você declarar uma função dentro de outra função, as variáveis ​​locais da função externa podem permanecer acessíveis depois de retornar dela. Isso é demonstrado acima, porque chamamos a função say2() depois que retornamos de sayHello2() . Observe que o código que chamamos faz referência ao text da variável, que era uma 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 ao text da variável. A função anônima pode referenciar o text que contém o valor 'Hello Bob' porque as variáveis ​​locais de sayHello2() foram mantidas secretamente ativas em um encerramento.

O gênio é que, em JavaScript, uma referência de função também possui uma referência secreta ao fechamento em que foi criada - semelhante a como os representantes são um ponteiro de método e uma referência secreta a um objeto.

Mais exemplos

Por alguma razão, os fechamentos 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). Eu recomendo trabalhar com os exemplos com cuidado até entender como eles funcionam. Se você começar a usar closures sem entender completamente como eles funcionam, você logo criaria 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 quadro de pilha permanecesse vivo na memória mesmo depois que a função externa existisse!

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 para o mesmo encerramento, porque elas são todas declaradas em 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 fechamento - as variáveis ​​locais de setupSomeGlobals() quando as três funções foram definidas.

Observe que no exemplo acima, se você chamar setupSomeGlobals() novamente, um novo fechamento (stack-frame!) setupSomeGlobals() criado. As antigas gLogNumber , gIncreaseNumber , gSetNumber são sobrescritas com novas 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 a cada vez que a função externa é chamada).

Exemplo 5

Este exemplo mostra que o encerramento contém quaisquer variáveis ​​locais que foram declaradas dentro da função externa antes de sair. Observe que a variável alice é realmente declarada após a função anônima. A função anônima é declarada primeiro e, quando essa função é chamada, ela pode acessar a variável alice porque alice está no mesmo escopo (o JavaScript faz a elevação variável ). Também sayAlice()() apenas chama diretamente a referência de função retornada de sayAlice() - é exatamente o mesmo 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"

Tricky: observe também que a variável say também está dentro do closure, e pode ser acessada por qualquer outra função que possa ser declarada dentro de sayAlice() , ou pode ser acessada recursivamente dentro da função interna.

Exemplo 6

Essa é uma pegadinha para muitas pessoas, então você precisa entender. Tenha muito cuidado se você estiver definindo uma função dentro de um loop: as variáveis ​​locais do fechamento podem não agir como você pode pensar primeiro.

Você precisa entender o recurso "içamento 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 na matriz resultante. Se você não está tão familiarizado com funções anônimas, pense em gosta:

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, há apenas um encerramento para as variáveis ​​locais para buildList (que são result , i e item ). Quando as funções anônimas são chamadas na linha fnlist[j]() ; todos eles usam o mesmo fechamento único, e eles usam o valor atual para i e item dentro daquele fechamento (onde i tenho um valor de 3 porque o loop foi concluído e o item tem um valor de 'item2' ). Note que estamos indexando a partir de 0, portanto, o item possui um valor de item2 . E o i ++ incrementará i para o valor 3 .

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

item0 undefined
item1 undefined
item2 undefined

Se a variável i também é definida usando let vez de var , a saída é:

item0 1
item1 2
item2 3

Exemplo 7

Neste exemplo final, cada chamada para a função principal cria um fechamento 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 completamente obscuro, então a melhor coisa a fazer é brincar com os exemplos. Ler uma explicação é muito mais difícil do que entender exemplos. Minhas explicações de fechamentos e frames de pilha, etc. não são tecnicamente corretas - são simplificações grosseiras destinadas a ajudar a entender. Uma vez que a idéia básica esteja pronta, você pode pegar os detalhes mais tarde.

Pontos finais:

  • Sempre que você usa a function dentro de outra função, um fechamento é usado.
  • Sempre que você usa eval() dentro de uma função, um fechamento é usado. O texto que você eval pode referenciar variáveis ​​locais da função, e dentro de eval você pode até mesmo criar novas variáveis ​​locais usando eval('var foo = …')
  • Quando você usa a new Function(…) (o construtor Function ) dentro de uma função, ela não cria um fechamento. (A nova função não pode referenciar as variáveis ​​locais da função externa.)
  • Um encerramento em JavaScript é como manter uma cópia de todas as variáveis ​​locais, exatamente como quando uma função foi encerrada.
  • É provavelmente melhor pensar que um encerramento é sempre criado apenas 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 toda vez que uma função com um encerramento é chamada (dado que a função contém uma declaração de função dentro dela, e uma referência àquela função interna é retornada ou uma referência externa é mantida de alguma forma ).
  • Duas funções podem parecer que têm o mesmo texto de origem, mas têm um comportamento completamente diferente devido ao seu fechamento "oculto". Eu não acho que o código JavaScript pode realmente descobrir se uma referência de função tem um encerramento ou não.
  • Se você estiver tentando fazer qualquer modificação dinâmica no código-fonte (por exemplo: myFunction = Function(myFunction.toString().replace(/Hello/,'Hola')); ), não funcionará se myFunction for um encerramento ( é claro, você nunca pensaria em fazer uma substituição de string de código-fonte em tempo de execução, mas ...).
  • É possível obter declarações de função dentro de declarações de função dentro de funções & mdash, e você pode obter encerramentos em mais de um nível.
  • Eu acho que normalmente um encerramento é um termo para ambas as funções junto com as variáveis ​​que são capturadas. Note que eu não uso essa definição neste artigo!
  • Eu suspeito que os closures em JavaScript sejam diferentes daqueles normalmente encontrados em linguagens funcionais.

Links

obrigado

Se você acabou de aprender o fechamento (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 email para morrisjohns.com (morris_closure @). Por favor, note que eu não sou um guru em JavaScript - nem em encerramentos.

O post original de Morris pode ser encontrado no Internet Archive .


Eu tenho um exemplo do mundo real onde usei o Node.js. A empresa onde trabalho tem um cliente que queria ter um site HTML simples e estático. Este site é para vender um item usando o PayPal e o cliente também queria ter um contador que mostrasse a quantidade de itens vendidos. O cliente deverá ter uma quantidade enorme de visitantes para este site. Eu decidi fazer o contador usando o Node.js e o framework Express.js .

O aplicativo Node.js foi simples. Obtenha o valor de itens vendidos de um banco de dados Redis , aumente o contador quando o item for vendido e sirva o valor do contador para os usuários por meio da API .

Algumas razões pelas quais eu escolhi usar o Node.js neste caso

  1. É muito leve e rápido. Houve mais de 200.000 visitas neste site em três semanas e recursos mínimos do servidor conseguiram lidar com tudo isso.
  2. O contador é realmente fácil de fazer para ser em tempo real.
  3. O Node.js foi fácil de configurar.
  4. Existem muitos módulos disponíveis gratuitamente. Por exemplo, encontrei um módulo Node.js para o PayPal.

Neste caso, o Node.js foi uma escolha incrível.





javascript function variables scope closures