what - Como funcionam os fechamentos de JavaScript?




what are closures in javascript (20)

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.


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

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

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

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

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

Mas, em JavaScript há essa coisa muito especial que funciona que é criada dentro de outras funções, também pode conhecer as variáveis ​​locais de seus pais e mantê-los enquanto eles 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 pode criar uma ou mais funções filho que compartilham as variáveis ​​secretas do lugar secreto.

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

Então, para viver, a criança tem que sair 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, embora Mary "não esteja mais correndo", a memória dela não está perdida e seu filho sempre lembrará seu nome e outros segredos que eles compartilharam durante seu tempo juntos.

Então, se você chamar a criança de "Alice", ela responderá

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

Isso é tudo que há para contar.


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 .


Você pode explicar o fechamento de uma criança de 5 anos? *

Eu ainda acho que a explicação do Google funciona muito bem e é concisa:

/*
*    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);​

* Pergunta AC #


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.


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.


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.

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.


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 encerramento . Ele sempre tem acesso a variáveis ​​definidas no escopo ao redor.

Como a 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 fechamento em JavaScript é uma função que trabalha com variáveis ​​não-locais definidas na função circundante já executada .

Fechamentos são freqüentemente 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 está usando uma função anônima, que foi executada uma vez. Mas não precisa ser. Pode ser nomeado (por exemplo mkdb) e executado posteriormente, gerando uma função de banco de dados toda vez que for invocada. Cada função gerada terá seu próprio objeto de banco de dados oculto. Outro exemplo de uso de closures é quando não retornamos uma função, mas um objeto contendo múltiplas funções para diferentes propósitos, cada uma dessas funções tendo acesso aos mesmos dados.


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.


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

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 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 , 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.


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.


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.


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.


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

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.


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.


OK, 6 anos de idade fecha fã. Você quer ouvir o exemplo mais simples de fechamento?

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

Aqui está como eu posso converter minha história de avião no 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");


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.


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




closures