Como funcionam os fechamentos de JavaScript?

7636

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

Vi o exemplo de esquema dado na Wikipedia, mas infelizmente não ajudou.

Zaheer Ahmed
fonte
391
Meu problema com essas e muitas respostas é que elas a abordam de uma perspectiva teórica abstrata, em vez de começar simplesmente a explicar por que os fechamentos são necessários em Javascript e as situações práticas em que você as utiliza. Você acaba com um artigo que você precisa ler, o tempo todo pensando "mas, por quê?". Eu simplesmente começaria com: fechamentos são uma maneira elegante de lidar com as duas realidades a seguir do JavaScript: a. o escopo está no nível da função, não no nível do bloco e, b. muito do que você faz na prática em JavaScript é assíncrono / orientado a eventos.
9133 Jeremy Burton
53
@Redsandro Por um lado, torna muito mais fácil escrever código orientado a eventos. Posso acionar uma função quando a página é carregada para determinar detalhes sobre o HTML ou os recursos disponíveis. Posso definir e definir um manipulador nessa função e ter todas essas informações de contexto disponíveis toda vez que o manipulador é chamado sem precisar consultá-lo novamente. Resolva o problema uma vez, reutilize em todas as páginas em que esse manipulador é necessário, com sobrecarga reduzida na re-chamada do manipulador. Você já viu os mesmos dados serem mapeados novamente duas vezes em um idioma que não os possui? Os fechamentos facilitam muito evitar esse tipo de coisa.
precisa
1
@ Erik Reppen, obrigado pela resposta. Na verdade, eu estava curioso sobre os benefícios desse closurecódigo difícil de ler , em vez do Object Literalque se reutiliza e reduz a sobrecarga da mesma forma, mas exige 100% menos código de quebra.
Redsandro
6
Para programadores Java, a resposta curta é que é a função equivalente a uma classe interna. Uma classe interna também mantém um ponteiro implícito para uma instância da classe externa e é usada com o mesmo objetivo (ou seja, criar manipuladores de eventos).
Boris van Schooten
8
Achei este exemplo prático muito útil: youtube.com/watch?v=w1s9PgtEoJs
Abhi

Respostas:

7360

Um encerramento é um par de:

  1. Uma função e
  2. Uma referência ao escopo externo dessa função (ambiente lexical)

Um ambiente lexical é parte de todo contexto de execução (quadro de pilha) e é um mapa entre identificadores (ou seja, nomes de variáveis ​​locais) e valores.

Toda função no JavaScript mantém uma referência ao seu ambiente lexical externo. Esta referência é usada para configurar o contexto de execução criado quando uma função é chamada. Essa referência permite que o código dentro da função "veja" variáveis ​​declaradas fora da função, independentemente de quando e onde a função é chamada.

Se uma função foi chamada por uma função, que por sua vez foi chamada por outra função, uma cadeia de referências a ambientes lexicais externos é criada. Essa cadeia é chamada de cadeia de escopo.

No código a seguir, innerforma um fechamento com o ambiente lexical do contexto de execução criado quando fooé chamado, fechando sobre a variável secret:

function foo() {
  const secret = Math.trunc(Math.random()*100)
  return function inner() {
    console.log(`The secret number is ${secret}.`)
  }
}
const f = foo() // `secret` is not directly accessible from outside `foo`
f() // The only way to retrieve `secret`, is to invoke `f`

Em outras palavras: em JavaScript, as funções carregam uma referência a uma "caixa de estado" privada, à qual somente elas (e quaisquer outras funções declaradas no mesmo ambiente lexical) têm acesso. Essa caixa de estado é invisível para o chamador da função, fornecendo um excelente mecanismo para ocultar e encapsular dados.

E lembre-se: as funções no JavaScript podem ser passadas como variáveis ​​(funções de primeira classe), o que significa que esses pares de funcionalidade e estado podem ser passados ​​pelo seu programa: semelhante à maneira como você pode passar uma instância de uma classe no C ++.

Se o JavaScript não tivesse encerramentos, mais estados teriam que ser passados ​​entre as funções explicitamente , tornando as listas de parâmetros mais longas e com o código mais barulhento.

Portanto, se você deseja que uma função sempre tenha acesso a um pedaço de estado particular, você pode usar um encerramento.

... e muitas vezes nós não queremos estado associado com uma função. Por exemplo, em Java ou C ++, quando você adiciona uma variável de instância privada e um método a uma classe, você está associando estado à funcionalidade.

Em C e na maioria dos outros idiomas comuns, após o retorno de uma função, todas as variáveis ​​locais não são mais acessíveis porque o quadro da pilha é destruído. No JavaScript, se você declarar uma função dentro de outra função, as variáveis ​​locais da função externa poderão permanecer acessíveis após o retorno dela. Dessa forma, no código acima, secretpermanece disponível para o objeto de função inner, depois que ele é retornado foo.

Usos dos fechamentos

Os fechamentos são úteis sempre que você precisar de um estado privado associado a uma função. Esse é um cenário muito comum - e lembre-se: o JavaScript não tinha uma sintaxe de classe até 2015 e ainda não possui uma sintaxe de campo privado. Os fechamentos atendem a essa necessidade.

Variáveis ​​de instância privada

No código a seguir, a função toStringfecha sobre os detalhes do carro.

function Car(manufacturer, model, year, color) {
  return {
    toString() {
      return `${manufacturer} ${model} (${year}, ${color})`
    }
  }
}
const car = new Car('Aston Martin','V8 Vantage','2012','Quantum Silver')
console.log(car.toString())

Programação Funcional

No código a seguir, a função innerfecha sobre ambos fne args.

function curry(fn) {
  const args = []
  return function inner(arg) {
    if(args.length === fn.length) return fn(...args)
    args.push(arg)
    return inner
  }
}

function add(a, b) {
  return a + b
}

const curriedAdd = curry(add)
console.log(curriedAdd(2)(3)()) // 5

Programação Orientada a Eventos

No código a seguir, a função onClickfecha sobre a variável BACKGROUND_COLOR.

const $ = document.querySelector.bind(document)
const BACKGROUND_COLOR = 'rgba(200,200,242,1)'

function onClick() {
  $('body').style.background = BACKGROUND_COLOR
}

$('button').addEventListener('click', onClick)
<button>Set background color</button>

Modularização

No exemplo a seguir, todos os detalhes da implementação estão ocultos dentro de uma expressão de função executada imediatamente. As funções ticke toStringfecham sobre o estado privado e as funções necessárias para concluir seu trabalho. Os fechamentos nos permitiram modularizar e encapsular nosso código.

let namespace = {};

(function foo(n) {
  let numbers = []
  function format(n) {
    return Math.trunc(n)
  }
  function tick() {
    numbers.push(Math.random() * 100)
  }
  function toString() {
    return numbers.map(format)
  }
  n.counter = {
    tick,
    toString
  }
}(namespace))

const counter = namespace.counter
counter.tick()
counter.tick()
console.log(counter.toString())

Exemplos

Exemplo 1

Este exemplo mostra que as variáveis ​​locais não são copiadas no fechamento: o fechamento mantém uma referência às próprias variáveis ​​originais . É como se o quadro da pilha permanecesse vivo na memória, mesmo após a saída da função externa.

function foo() {
  let x = 42
  let inner  = function() { console.log(x) }
  x = x+1
  return inner
}
var f = foo()
f() // logs 43

Exemplo 2

No código a seguir, três métodos log, incremente updatetoda apertada sobre o mesmo ambiente lexical.

E toda vez que createObjecté chamado, um novo contexto de execução (quadro da pilha) é criado e uma variável completamente nova x, e um novo conjunto de funções ( logetc.) são criados, que fecham sobre essa nova variável.

function createObject() {
  let x = 42;
  return {
    log() { console.log(x) },
    increment() { x++ },
    update(value) { x = value }
  }
}

const o = createObject()
o.increment()
o.log() // 43
o.update(5)
o.log() // 5
const p = createObject()
p.log() // 42

Exemplo 3

Se você estiver usando variáveis ​​declaradas var, tenha cuidado para entender qual variável você está fechando. Variáveis ​​declaradas usando varsão içadas. Isso é muito menos problemático no JavaScript moderno devido à introdução de lete const.

No código a seguir, toda vez que o loop inneré criado, uma nova função é encerrada i. Mas, como var ié içada fora do loop, todas essas funções internas se fecham sobre a mesma variável, o que significa que o valor final de i(3) é impresso três vezes.

function foo() {
  var result = []
  for (var i = 0; i < 3; i++) {
    result.push(function inner() { console.log(i) } )
  }
  return result
}

const result = foo()
// The following will print `3`, three times...
for (var i = 0; i < 3; i++) {
  result[i]() 
}

Pontos finais:

  • Sempre que uma função é declarada em JavaScript, um fechamento é criado.
  • Retornar um functionde dentro de outra função é o exemplo clássico de um fechamento, porque o estado dentro da função externa está implicitamente disponível para a função interna retornada, mesmo após a função externa ter concluído a execução.
  • Sempre que você usa eval()dentro de uma função, um fechamento é usado. O texto você evalpode referenciar variáveis ​​locais da função e, no modo não estrito, você pode até criar novas variáveis ​​locais usando eval('var foo = …').
  • Quando você usa new Function(…)(o construtor Function ) dentro de uma função, ela não fecha seu ambiente lexical: fecha-se sobre o contexto global. A nova função não pode fazer referência às variáveis ​​locais da função externa.
  • Um fechamento no JavaScript é como manter uma referência ( NÃO uma cópia) ao escopo no momento da declaração da função, que por sua vez mantém uma referência ao seu escopo externo e assim por diante, até o objeto global no topo da a cadeia de escopo.
  • Um fechamento é criado quando uma função é declarada; esse fechamento é usado para configurar o contexto de execução quando a função é chamada.
  • Um novo conjunto de variáveis ​​locais é criado toda vez que uma função é chamada.

Ligações

Ben Aston
fonte
74
Parece bom: "Um fechamento no JavaScript é como manter uma cópia de todas as variáveis ​​locais, exatamente como estavam quando uma função foi encerrada". Mas é enganoso por algumas razões. (1) A chamada de função não precisa sair para criar um fechamento. (2) Não é uma cópia dos valores das variáveis ​​locais, mas das próprias variáveis. (3) Não diz quem tem acesso a essas variáveis.
dlaliberte
27
O exemplo 5 mostra uma "pegadinha" em que o código não funciona conforme o esperado. Mas não mostra como corrigi-lo. Esta outra resposta mostra uma maneira de fazê-lo.
Matt
190
Eu gosto de como este post começa com grandes letras em negrito dizendo "Closures Are Not Magic" e termina seu primeiro exemplo com "A mágica é que, no JavaScript, uma referência de função também tem uma referência secreta ao fechamento em que foi criado".
Andrew Macheret
6
O exemplo # 3 é misturar fechamentos com içamento de javascripts. Agora, acho que explicar apenas os fechamentos é difícil o suficiente sem trazer o comportamento de elevação. Isso me ajudou muito: Closures are functions that refer to independent (free) variables. In other words, the function defined in the closure 'remembers' the environment in which it was created.do developer.mozilla.org/en-US/docs/Web/JavaScript/Closures
caramba
3
O ECMAScript 6 pode mudar algo neste ótimo artigo sobre fechamento. Por exemplo, se você usar em let i = 0vez de var i = 0no Exemplo 5, ele testList()imprimirá o que deseja originalmente.
Nier
3989

Toda função em JavaScript mantém um link para seu ambiente lexical externo. Um ambiente lexical é um mapa de todos os nomes (por exemplo, variáveis, parâmetros) dentro de um escopo, com seus valores.

Portanto, sempre que você vir a functionpalavra - chave, o código dentro dessa função terá acesso a variáveis ​​declaradas fora da função.

function foo(x) {
  var tmp = 3;

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

  bar(10);
}

foo(2);

Isso registrará 16porque a função barfecha sobre o parâmetro xe a variável tmp, ambas existentes no ambiente lexical da função externafoo .

Função bar, juntamente com seu vínculo com o ambiente lexical da função, fooé um fechamento.

Uma função não precisa retornar para criar um fechamento. Simplesmente em virtude de sua declaração, toda função se fecha sobre seu ambiente lexical, formando 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(10); // 16
bar(10); // 17

A função acima também registrará 16, porque o código interno barainda pode se referir ao argumento xe à variável tmp, mesmo que eles não estejam mais diretamente no escopo.

No entanto, como tmpainda está pendurado no interior bardo fechamento, está disponível para ser incrementado. Ele será incrementado toda vez que você ligarbar .

O exemplo mais simples de fechamento é o seguinte:

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 é chamada, um novo contexto de execução ecé criado. Juntamente com os argumentos da função e o objeto de destino, esse contexto de execução também recebe um link para o ambiente lexical do contexto de execução de chamada, o que significa que as variáveis ​​declaradas no ambiente lexical externo (no exemplo acima, ambos ae b) estão disponíveis em ec.

Toda função cria um fechamento porque toda função tem um link para seu ambiente lexical externo.

Observe que as próprias variáveis são visíveis de dentro de um fechamento, não de cópias.

Ali
fonte
24
@feeela: Sim, todas as funções JS criam um fechamento. As variáveis ​​que não são referenciadas provavelmente se qualificam para a coleta de lixo nos mecanismos JS modernos, mas isso não muda o fato de que, quando você cria um contexto de execução, esse contexto tem uma referência ao contexto de execução que o envolve, e suas variáveis, e essa função é um objeto com potencial para ser realocado para um escopo de variável diferente, mantendo a referência original. Esse é o fechamento.
@ Ali, eu acabei de descobrir que o jsFiddle que forneci não prova nada, uma vez que deletefalha. No entanto, o ambiente lexical que a função carregará como [[Scope]] (e, finalmente, usar como base para seu próprio ambiente lexical quando invocado) é determinado quando a instrução que define a função é executada. Isso significa que a função está fechando o conteúdo INTEIRO do escopo em execução, independentemente de quais valores ela realmente se refere e se ela escapa ao escopo. Por favor, olhe seções 13.2 e 10 em spec
Asad Saeeduddin
8
Essa foi uma boa resposta até tentar explicar tipos e referências primitivas. Isso é completamente errado e fala sobre literais sendo copiados, o que realmente não tem nada a ver com nada.
Ry-
12
Os fechamentos são a resposta do JavaScript para a programação orientada a objetos e baseada em classe. O JS não é baseado em classe, portanto, era necessário encontrar outra maneira de implementar algumas coisas que não poderiam ser implementadas de outra forma.
Bartłomiej Zalewski
2
essa deve ser a resposta aceita. A mágica nunca acontece na função interna. Isso acontece quando atribuir a função externa a uma variável. Isso cria um novo contexto de execução para a função interna, para que a "variável privada" possa ser acumulada. É claro que, desde a variável à qual a função externa atribuída manteve o contexto. A primeira resposta apenas torna a coisa toda mais complexa sem explicar o que realmente acontece lá.
Albert Gao
2442

PREFÁCIO: esta resposta foi escrita quando a pergunta 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 por si mesmo." Bem, tentei explicar o fechamento de JS para um amigo de 27 anos e falhei completamente.

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

Tenho certeza de que fui uma das únicas pessoas que tentaram levar a pergunta inicial literalmente. Desde então, a pergunta mudou várias vezes, então minha resposta agora pode parecer incrivelmente boba e fora de lugar. Espero que a ideia geral da história continue sendo divertida para alguns.


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:

Havia uma princesa ...

function princess() {

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

    var adventures = [];

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

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

    /* ... */

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

    return {

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

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

Mas tudo o que veriam é uma garotinha ...

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 só existiam dentro da imaginação da menininha.

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

... é realmente uma princesa com uma garotinha por dentro.

Jacob Swartwood
fonte
340
Eu amo essa explicação, verdadeiramente. Para quem lê e não segue, a analogia é a seguinte: a função princess () é um escopo complexo que contém dados privados. Fora da função, os dados privados não podem ser vistos ou acessados. A princesa mantém os unicórnios, dragões, aventuras etc. em sua imaginação (dados privados) e os adultos não podem vê-los por si mesmos. MAS a imaginação da princesa é capturada no fechamento da story()função, que é a única interface que a littleGirlinstância expõe no mundo da magia.
Patrick M
Então, aqui storyestá o fechamento, mas, se o código tivesse sido var story = function() {}; return story;, littleGirlseria o fechamento. Pelo menos, é a impressão que recebo do uso de métodos 'privados' pelo MDN com fechamentos : "Essas três funções públicas são encerramentos que compartilham o mesmo ambiente".
23416 icc97
16
@ icc97, sim, storyé um fechamento que faz referência ao ambiente fornecido no escopo de princess. princesstambém é outro fechamento implícito , ou seja, o princesse o littleGirlcompartilhariam qualquer referência a uma parentsmatriz que existiria no ambiente / escopo em que littleGirlexiste e princessestá definido.
Jacob Swartwood
6
@BenjaminKrupp Adicionei um comentário de código explícito para mostrar / implicar que há mais operações no corpo do princessque o que está escrito. Infelizmente, esta história está um pouco fora do lugar neste tópico. Originalmente, a pergunta era "explicar os fechamentos de JavaScript para os 5 anos"; minha resposta foi a única que tentou fazer isso. Não duvido que teria falhado miseravelmente, mas pelo menos essa resposta poderia ter tido a chance de manter o interesse de uma criança de 5 anos.
Jacob Swartwood
11
Na verdade, para mim, isso fazia todo o sentido. E devo admitir que finalmente entender um fechamento de JS usando contos de princesas e aventuras me faz sentir meio estranho.
Cristalize
753

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

Sobre o desenvolvimento infantil: 5 a 7 anos , diz:

Seu filho poderá seguir as instruções em duas etapas. Por exemplo, se você disser ao seu filho: "Vá para 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 fechamentos, da seguinte maneira:

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

Podemos codificar isso em JavaScript assim:

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

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

var kitchen = makeKitchen();

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

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

  • Cada vez que makeKitchen()é chamado, um novo fechamento é criado com seu própriotrashBags .
  • A trashBagsvariável é local para o interior de cada cozinha e não é acessível fora, mas a função interna nogetTrashBag propriedade tem acesso a ela.
  • Toda chamada de função cria um fechamento, mas não há 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 getTrashBagfunção faz isso aqui.
dlaliberte
fonte
6
Na verdade, de maneira confusa, a chamada da função makeKitchen é o fechamento real, não o objeto da cozinha que ele retorna.
dlaliberte
6
Passando pelos outros, encontrei essa resposta como a maneira mais fácil de explicar sobre o que e por que os closures.is.
Chetabahana 12/08
3
Muito cardápio e aperitivo, carne e batatas insuficientes. Você poderia melhorar essa resposta com apenas uma frase curta como: "Um fechamento é o contexto selado de uma função, por falta de qualquer mecanismo de escopo fornecido pelas classes".
Staplerfahrer
584

The Straw Man

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

Solução bastante óbvia

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

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

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

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

Agora isso vai funcionar, mas invade o escopo externo ao adicionar uma variável, cujo único objetivo é acompanhar a contagem. Em algumas situações, isso seria preferível, pois seu aplicativo externo pode precisar acessar essas informações. Mas, nesse caso, estamos apenas alterando o comportamento de cada terceiro clique, portanto, é preferível incluir essa funcionalidade dentro do manipulador de eventos .

Considere esta opção

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

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

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

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

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

Observe algumas coisas aqui.

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

Um fechamento simples de uma linha

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

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

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

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

Além disso, esse estado de variável privada é totalmente acessível, para leituras e atribuições às suas variáveis ​​de escopo privadas.

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

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

jondavidjohn
fonte
11
Não concordo com a sua definição do que é um fechamento. Não há razão para ter que se auto-invocar. É também uma simplista bit (e imprecisa) para dizer que tem que ser "devolvida" (lotes de discussão sobre este nos comentários da resposta superior a esta pergunta)
James Montagne
40
@ James, mesmo que você não concorde, o exemplo dele (e todo o post) é um dos melhores que eu já vi. Embora a pergunta não seja antiga e resolvida para mim, ela merece um +1.
e-satis
84
"Preciso saber quantas vezes um botão foi clicado e fazer algo a cada terceiro clique ..." Isso chamou minha atenção. Um caso de uso e a solução que mostram como um fechamento não é uma coisa tão misteriosa e que muitos de nós os escrevemos, mas não sabíamos exatamente o nome oficial.
precisa saber é o seguinte
Bom exemplo, porque mostra que "count" no segundo exemplo retém o valor de "count" e não é redefinido para 0 cada vez que o "elemento" é clicado. Muito informativo!
Adam
+1 para o comportamento de fechamento . Podemos limitar o comportamento de fechamento a funções em javascript ou esse conceito também pode ser aplicado a outras estruturas da linguagem?
Dziamid
493

É difícil explicar os fechamentos porque são usados ​​para fazer funcionar algum comportamento que todo mundo espera intuitivamente que funcione de qualquer maneira. Acho que a melhor maneira de explicá-las (e da maneira que eu aprendi o que eles fazem) é de 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 fechamentos? Apenas substitua a chamada na última linha pelo corpo do 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ão o definimos no escopo atual. A única solução é deixar plus5 levar seu escopo (ou melhor, o escopo de seus pais). Dessa forma, xé bem definido e está vinculado ao valor 5.

Konrad Rudolph
fonte
11
Esse é exatamente o tipo de exemplo que leva muitas pessoas a pensar que são os valores usados ​​na função retornada, não a variável mutável em si. Se fosse alterado para "return x + = y", ou melhor ainda, essa e outra função "x * = y", seria claro que nada está sendo copiado. Para as pessoas que costumam empilhar quadros, imagine usar quadros de heap, que podem continuar existindo após o retorno da função.
Matt
14
@ Matt eu discordo. Um exemplo não deve documentar exaustivamente todas as propriedades. Ele deve ser redutivo e ilustrar a característica marcante de um conceito. O OP pediu uma explicação simples (“para uma criança de seis anos”). Tome a resposta aceita: ela falha totalmente em fornecer uma explicação concisa, precisamente porque tenta ser exaustiva. (Eu concordo com você que é uma propriedade importante de JavaScript que a ligação é por referência e não por valor ... mas, novamente, uma explicação bem sucedido é aquele que reduz ao mínimo.)
Konrad Rudolph
@KonradRudolph Gosto do estilo e da brevidade do seu exemplo. Eu simplesmente recomendo alterá-lo um pouco para que a parte final, "A única solução é ...", se torne verdadeira. Atualmente, há na verdade uma outra solução, mais simples de seu cenário, o que não corresponde à continuações javascript, e faz correspondem a um equívoco comum sobre o continuações são. Assim, o exemplo em sua forma atual é perigoso. Isso não tem a ver com listar exaustivamente as propriedades, mas com a compreensão do que x está na função retornada, que é, afinal, o ponto principal.
Matt
@ Matt Hmm, não sei se entendi bem você, mas começo a perceber que você pode ter um argumento válido. Como os comentários são muito curtos, você poderia explicar o que você quer dizer com uma essência / pasta ou uma sala de bate-papo? Obrigado.
Konrad Rudolph
2
@KonradRudolph Acho que não estava claro sobre o objetivo de x + = y. O objetivo era apenas mostrar que as chamadas repetidas para a função retornada continuam usando a mesma variável x (em oposição ao mesmo valor , que as pessoas imaginam estar "inserido" quando a função é criada). É como os dois primeiros alertas no seu violino. O objetivo de uma função adicional x * = y seria mostrar que várias funções retornadas compartilham o mesmo x.
Matt
379

TLDR

Um fechamento é um link entre uma função e seu ambiente lexical externo (isto é, conforme escrito), de modo que os identificadores (variáveis, parâmetros, declarações de função etc.) definidos nesse ambiente sejam visíveis de dentro da função, independentemente de quando ou de onde a função é chamada.

Detalhes

Na terminologia da especificação ECMAScript, pode-se dizer que um fechamento é implementado pela [[Environment]]referência de cada objeto de função, que aponta para o ambiente lexical no qual a função é definida.

Quando uma função é chamada pelo [[Call]]método interno , a [[Environment]]referência no objeto de função é copiada na referência do ambiente externo do registro de ambiente do contexto de execução recém-criado (quadro de pilha).

No exemplo a seguir, a função ffecha-se sobre o ambiente lexical do contexto de execução global:

function f() {}

No exemplo a seguir, a função hfecha-se sobre o ambiente lexical da função g, que, por sua vez, fecha-se sobre o ambiente lexical do contexto de execução global.

function g() {
    function h() {}
}

Se uma função interna for retornada por uma externa, o ambiente lexical externo persistirá após o retorno da função externa. Isso ocorre porque o ambiente lexical externo precisa estar disponível se a função interna for eventualmente invocada.

No exemplo a seguir, a função se jfecha sobre o ambiente lexical da função i, o que significa que a variável xé visível a partir da função interna j, muito depois que a função iconcluir a execução:

function i() {
    var x = 'mochacchino'
    return function j() {
        console.log('Printing the value of x, from within function j: ', x)
    }
} 

const k = i()
setTimeout(k, 500) // invoke k (which is j) after 500ms

Em um fecho, as variáveis do ambiente exterior lexical -se são disponíveis, não cópias.

function l() {
  var y = 'vanilla';

  return {
    setY: function(value) {
      y = value;
    },
    logY: function(value) {
      console.log('The value of y is: ', y);
    }
  }
}

const o = l()
o.logY() // The value of y is: vanilla
o.setY('chocolate')
o.logY() // The value of y is: chocolate

A cadeia de ambientes lexicais, vinculada entre contextos de execução por meio de referências ao ambiente externo, forma uma cadeia de escopo e define os identificadores visíveis de qualquer função.

Observe que, na tentativa de melhorar a clareza e a precisão, essa resposta foi substancialmente alterada da original.

Ben
fonte
56
Uau, nunca soube que você poderia usar substituições de string console.logassim. Se alguém estiver interessado, há mais: developer.mozilla.org/pt-BR/docs/DOM/…
Flash
7
Variáveis ​​que estão na lista de parâmetros da função também fazem parte do fechamento (por exemplo, não se limitam apenas a var).
Thomas Eding
Os fechamentos parecem mais objetos e classes etc. Não sei por que muitas pessoas não comparam esses dois - seria mais fácil para nós, novatos, aprender!
Almaruf 23/05/19
376

OK, fã de fechamentos de 6 anos de idade. Deseja ouvir o exemplo mais simples de fechamento?

Vamos imaginar a próxima situação: um motorista está sentado em um carro. Aquele carro está dentro de um avião. Avião está no aeroporto. A capacidade do motorista de acessar 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ê completa 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");

Max Tkachenko
fonte
26
Bem jogado e responde ao cartaz original. Eu acho que essa é a melhor resposta. Eu usaria a bagagem de maneira semelhante: imagine que você vá para a casa da vovó e guarde seu estojo nintendo DS com cartões de jogo dentro do seu estojo, mas depois coloque o estojo dentro da mochila e coloque cartões de jogo nos bolsos da mochila, e ENTÃO você coloca a coisa toda em uma mala grande com mais cartões de jogo nos bolsos da mala. Quando você chega na casa da vovó, pode jogar qualquer jogo no seu DS, desde que todos os casos externos estejam abertos. ou algo nesse sentido.
Slartibartfast
366

Esta é uma tentativa de esclarecer vários (possíveis) mal-entendidos sobre fechamentos 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 de fechamento não precisa retornar para que seu fechamento seja criado. Em vez disso, você pode atribuir sua função interna a uma variável em um escopo externo ou passá-la como argumento para outra função na qual ela pode ser chamada imediatamente ou a qualquer momento depois. Portanto, o fechamento da função de fechamento provavelmente é criado assim que a função de fechamento é chamada, já que qualquer função interna tem acesso a esse fechamento sempre que a função interna é chamada, antes ou depois do retorno da função de fechamento.
  • Um fechamento não faz referência a uma cópia dos valores antigos das variáveis ​​em seu escopo. As próprias variáveis ​​fazem parte do fechamento e, portanto, o valor visto ao acessar uma dessas variáveis ​​é o valor mais recente no momento em que é acessado. É por isso que funções internas criadas dentro de loops podem ser complicadas, pois 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 variáveis ​​do fechamento que o contém, até o escopo global.
  • Os fechamentos usam memória, mas não causam vazamentos de memória, já que o JavaScript, por si só, limpa suas próprias estruturas circulares que não são referenciadas. Vazamentos de memória do Internet Explorer envolvendo fechamentos são criados quando ele não desconecta os valores de atributo DOM que fazem referência a fechamentos, mantendo assim referências a estruturas circulares.
dlaliberte
fonte
15
James, eu disse que o fechamento "provavelmente" foi criado no momento da chamada da função de fechamento, porque é plausível que uma implementação possa adiar a criação de um fechamento até algum tempo depois, quando ele decide que o fechamento é absolutamente necessário. Se não houver nenhuma função interna definida na função anexa, nenhum fechamento será necessário. Portanto, talvez isso possa esperar até que a primeira função interna seja criada para criar um fechamento fora do contexto de chamada da função envolvente.
dlaliberte
9
@ Beterraba-Beterraba Suponha que temos uma função interna que é passada para outra função onde é usada antes do retorno da função externa, e suponha que também retornemos a mesma função interna da função externa. É identicamente a mesma função em ambos os casos, mas você está dizendo que antes que a função externa retorne, a função interna é "vinculada" à pilha de chamadas, enquanto que depois que ela retorna, a função interna repentinamente é vinculada a um fechamento. Comporta-se de forma idêntica nos dois casos; a semântica é idêntica, então você não está apenas falando sobre detalhes de implementação?
dlaliberte
7
@ Beterraba-beterraba, obrigado pelo seu feedback, e estou feliz por ter você pensando. Ainda não vejo nenhuma diferença semântica entre o contexto ativo da função externa e o mesmo contexto quando ele se torna um fechamento quando a função retorna (se eu entender sua definição). A função interna não se importa. A coleta de lixo não se importa, pois a função interna mantém uma referência ao contexto / fechamento de qualquer maneira, e o chamador da função externa apenas baixa sua referência ao contexto da chamada. Mas é confuso para as pessoas, e talvez seja melhor chamá-lo de contexto de chamada.
dlaliberte
9
É difícil ler esse artigo, mas acho que realmente suporta o que estou dizendo. Ele diz: "Um fechamento é formado retornando um objeto de função [...] ou atribuindo diretamente uma referência a esse objeto de função, por exemplo, uma variável global". Não quero dizer que o GC seja irrelevante. Em vez disso, por causa do GC e porque a função interna está anexada ao contexto de chamada da função externa (ou [[escopo]] como o artigo diz)), não importa se a chamada da função externa retorna porque essa ligação com o interno função é a coisa importante.
precisa saber é o seguinte
3
Ótima resposta! Uma coisa que você deve adicionar é que todas as funções fecham sobre todo o conteúdo do escopo em execução no qual estão definidas. Não importa se eles se referem a algumas ou nenhuma das variáveis ​​do escopo pai: uma referência ao ambiente lexical do escopo pai é armazenada como [[Escopo]] incondicionalmente. Isso pode ser visto na seção sobre criação de funções na especificação ECMA.
Asad Saeeduddin
236

Eu escrevi um post de blog há algum tempo explicando os fechamentos. Aqui está o que eu disse sobre fechamentos em termos de por que você quer um.

Os fechamentos são uma maneira de permitir que uma função tenha variáveis ​​privadas persistentes - ou seja, variáveis ​​que apenas uma função conhece, onde pode acompanhar as informações dos tempos anteriores em que foi executada.

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

Post completo:

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

Nathan Long
fonte
Então, o principal benefício do fechamento poderia ser enfatizado com este exemplo? Digamos que eu tenho uma função emailError (sendToAddress, errorString) Eu poderia dizer devError = emailError("[email protected]", errorString)e ter minha própria versão personalizada de uma função emailError compartilhada?
Devin G Rhode
Esta explicação e o exemplo perfeito associado no link para (coisa de fechamento) é a melhor maneira de entender os fechamentos e deve estar no topo!
HopeKing 5/05/19
215

Os fechamentos são simples:

O exemplo simples a seguir abrange todos os principais pontos de fechamento 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 principal: Cada chamada para make_calculatorcria uma nova variável local n, que continua a ser utilizável pela calculadora adde multiplyfunciona por muito tempo depois dos make_calculatorretornos.

Se você conhece os quadros de pilha, essas calculadoras parecem estranhas: como elas podem continuar acessando napós make_calculatorretornos? 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 fechamentos .

Isso é praticamente tudo o que há para fechamentos.



* Por exemplo, abrange todos os pontos do artigo "Fechamentos para manequins", fornecidos em outra resposta , exceto o exemplo 6, que mostra simplesmente que as variáveis ​​podem ser usadas antes de serem declaradas, um fato interessante a ser conhecido, mas completamente não relacionado aos fechamentos. Ele também abrange todos os pontos da resposta aceita , exceto os pontos (1) que funções copiam seus argumentos em variáveis ​​locais (argumentos da função nomeada) e (2) que a cópia de números cria um novo número, mas a referência de objeto fornece outra referência ao mesmo objeto. Também é bom saber disso, mas novamente não relacionado a fechamentos. Também é muito semelhante ao exemplo nesta resposta, mas um pouco mais curto e menos abstrato. Não cobre o ponto deesta resposta ou atual este comentário, que é o JavaScript que dificulta a conexão dovalor de uma variável de loop em sua função interna: A etapa "plug-in" pode ser realizada apenas 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 da função auxiliar, em vez de ter alguma coisa conectada.) Novamente, muito útil ao criar fechamentos, mas não faz parte do que é um fechamento ou como funciona. Existe uma confusão adicional devido aos fechamentos que funcionam de maneira diferente em linguagens funcionais como o ML, em que as variáveis ​​são vinculadas a valores e não a espaço de armazenamento, fornecendo um fluxo constante de pessoas que entendem os fechamentos de uma maneira (ou seja, a maneira "conectada") que é simplesmente incorreto para JavaScript, onde as variáveis ​​estão sempre ligadas ao espaço de armazenamento e nunca aos valores.

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

Matt
fonte
O que aconteceria se você chamasse: second_calculator = first_calculator (); em vez de second_calculator = make_calculator (); ? Deve ser o mesmo, certo?
Ronen Festinger
4
@ Ronen: Como first_calculatoré um objeto (não uma função), você não deve usar parênteses second_calculator = first_calculator;, pois é uma atribuição, não uma chamada de função. Para responder sua pergunta, haveria apenas uma chamada para make_calculator, para que apenas uma calculadora fosse feita e as variáveis ​​first_calculator e second_calculator se referissem à mesma calculadora, portanto as respostas seriam 3, 403, 4433, 44330.
Matt
204

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

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

Magne
fonte
200

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

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

Prova de que este exemplo cria um fechamento, mesmo que a função interna não retorne

* Questão AC #

Chris S
fonte
11
O código está "correto", como um exemplo de fechamento, mesmo que não atenda à parte do comentário sobre o uso do fechamento após o retorno da função externa. Portanto, não é um ótimo exemplo. Existem muitas outras maneiras de usar um fechamento que não envolvem o retorno da função interna. por exemplo, innerFunction pode ser passado para outra função em que é chamado imediatamente ou armazenado e chamado algum tempo depois e, em todos os casos, ele tem acesso ao contexto outerFunction que foi criado quando foi chamado.
dlaliberte
6
@ syockit Não, Moss está errado. Um fechamento é criado independentemente de a função escapar do escopo em que está definida, e uma referência criada incondicionalmente ao ambiente lexical do pai torna todas as variáveis ​​no escopo pai disponíveis para todas as funções, independentemente de serem invocadas para fora ou dentro o escopo em que eles foram criados.
Asad Saeeduddin
176

Costumo aprender melhor comparando BOM / MAU. Eu gosto de ver o código de trabalho seguido pelo código não funcional que alguém provavelmente encontrará. Eu montei um jsFiddle que faz uma comparação e tenta resumir as diferenças para as explicações mais simples que eu possa ter.

Fechamentos feitos corretamente:

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 todas as iterações do loop. Observe que eu nomeei a variável npara destacar que ela é 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 n dentro desse escopo.

  • Dentro de cada escopo, né vinculado a qualquer valor que tinha quando createClosure(n)foi chamado, portanto a função aninhada que é retornada sempre retornará o valor nque tinha quando createClosure(n)foi chamado.

Fechamentos feitos incorretamente:

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 o array completo, que à primeira vista parece mais intuitivo.

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

  • Dentro desta função, uma variável denominada indexé definida. O loop é executado e adiciona funções à matriz que retorna index. Observe que indexé definido na createClosureArrayfunção que apenas é invocada uma vez.

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

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

  • Depois que o loop terminou e indexfoi modificado, o valor final era 10, portanto, todas as funções adicionadas à matriz retornam o valor da indexvariável única que agora está definida como 10.

Resultado

FECHAMENTOS DIREITOS
n = 0
n = 1
n = 2
n = 3
n = 4
n = 5
n = 6
n = 7
n = 8
n = 9

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

Chev
fonte
1
Adição agradável, obrigado. Apenas para deixar mais claro, pode-se imaginar como a matriz "ruim" é criada no loop "ruim" com cada iteração: 1ª iteração: [function () {return 'n =' + 0;}] 2ª iteração: [( function () {return 'n =' + 1;}), (function () {return 'n =' + 1;})] terceira iteração: [(function () {return 'n =' + 2;}) , (function () {return 'n =' + 2;}), (function () {return 'n =' + 2;})] etc. Portanto, sempre que o valor do índice muda, ele é refletido em todas as funções já adicionado à matriz.
Alex Alexeev
3
Usar letpara varcorrige a diferença.
Rupam Datta 16/10/19
Não está aqui "Fechamento bem feito" é um exemplo de "fechamento dentro de fechamento"?
TechnicalSmile
Quero dizer, toda função é tecnicamente um fechamento, mas a parte importante é que a função defina uma nova variável dentro. A função que obtém retorna apenas referências ncriadas em um novo fechamento. Nós apenas retornamos uma função para que possamos armazená-la na matriz e invocá-la mais tarde.
Chev
Se você quiser apenas armazenar o resultado na matriz na primeira iteração, então você poderia inline assim: arr[index] = (function (n) { return 'n = ' + n; })(index);. Mas então você está armazenando a string resultante na matriz, em vez de uma função para invocar, o que derrota o ponto do meu exemplo.
Chev
164

Wikipedia sobre fechamentos :

Na ciência da computação, um fechamento é 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 circundante.

Como a construção de definição de escopo no JavaScript é uma função , não um bloco de código como em muitas outras linguagens, o que geralmente queremos dizer com fechamento no JavaScript é uma função que trabalha com variáveis ​​não-locais definidas na função circundante já executada .

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

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

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

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

ems

O exemplo acima está usando uma função anônima, que foi executada uma vez. Mas não precisa ser assim. Ele pode ser nomeado (por exemplo mkdb) e executado posteriormente, gerando uma função de banco de dados toda vez que é chamada. Toda função gerada terá seu próprio objeto de banco de dados oculto. Outro exemplo de uso de fechamentos é quando não retornamos uma função, mas um objeto que contém várias funções para propósitos diferentes, cada uma dessas funções tendo acesso aos mesmos dados.

mykhal
fonte
2
Esta é a melhor explicação para fechamentos de JavaScript. Deve ser a resposta escolhida. O resto é divertido o suficiente, mas este é realmente útil de uma maneira prática para os codificadores JavaScript do mundo real.
geoidesic
136

Eu montei um tutorial interativo em JavaScript para explicar como funcionam os fechamentos. O que é um fechamento?

Aqui está um dos exemplos:

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

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

var y = g();
// y is 42 here
Nathan Whitehead
fonte
128

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

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

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

Sempre 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, como as variáveis ​​são recriadas toda vez que a função é chamada e ninguém mais as conhece, deve haver um local secreto onde elas são armazenadas. Pode ser chamado de Câmara Secreta ou pilha ou escopo local mas isso realmente não importa. Sabemos que eles estão lá, em algum lugar, escondidos na memória.

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

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

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

Mas o triste é que, se a criança também for uma variável privada de sua função pai, ela também morrerá quando o pai terminar, e os segredos morrerão com ela.

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, mesmo que Maria "não esteja mais fugindo", a memória dela não se perde e seu filho sempre se lembrará de seu nome e de outros segredos que eles compartilharam durante o tempo que passaram juntos.

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

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

É tudo o que há para dizer.

Tero Tolonen
fonte
15
Esta é a explicação que fez mais sentido para mim, porque ela não assume conhecimento prévio significativo de termos técnicos. A explicação mais votada aqui assume que a pessoa que não entende os fechamentos tem um entendimento completo e completo de termos como 'escopo lexical' e 'contexto de execução' - embora eu possa entendê-los conceitualmente, acho que não sou tão confortável com os detalhes deles como eu deveria estar, e a explicação sem nenhum jargão é o que fez os fechamentos finalmente clicarem em mim, obrigado. Como bônus, acho que também explica qual escopo é muito conciso.
Emma W
103

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

Aqui está um encerramento:

var a = 42;

function b() { return a; }

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


Não há razão para acreditar que o fechamento seja um truque complexo de design para resolver problemas específicos. Não, os fechamentos 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 ele permite que você faça pode ser mais espetacular, veja outras respostas.

floribon
fonte
5
Parece que essa resposta não ajuda a desconfiar as pessoas. Um equivalente aproximado em uma linguagem de programação tradicional pode ser criar b () como um método em um objeto que também possui uma constante ou propriedade privada a. Na minha opinião, a surpresa é que o objeto de escopo JS efetivamente fornece acomo uma propriedade e não como uma constante. E você só notará esse comportamento importante se modificá-lo, como emreturn a++;
Jon Coombs
1
Exatamente o que Jon disse. Antes de finalmente fechar as portas, tive dificuldade em encontrar exemplos práticos. Sim, floribon criou um fechamento, mas, para não me educar, isso não me ensinaria absolutamente nada.
Chev
3
Isso não define o que é um fechamento - é apenas um exemplo que usa um. E não aborda as nuances do que acontece quando o escopo termina; Eu não acho que alguém tenha uma pergunta sobre o escopo lexical quando todos os escopos ainda estão por aí, e especialmente no caso de uma variável global.
precisa
92

Exemplo para o primeiro ponto por dlaliberte:

Um fechamento não é criado apenas quando você retorna uma função interna. De fato, a função de fechamento não precisa retornar. Em vez disso, você pode atribuir sua função interna a uma variável em um escopo externo ou passá-la como argumento para outra função em que ela possa 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);
someisaac
fonte
Pequenos esclarecimentos sobre uma possível ambiguidade. Quando eu disse "De fato, a função de fechamento não precisa retornar". Não quis dizer "não retorne valor", mas "ainda ativo". Portanto, o exemplo não mostra esse aspecto, embora mostre outra maneira pela qual a função interna pode ser passada para o escopo externo. O ponto principal que eu estava tentando destacar é sobre o momento da criação do fechamento (para a função de fechamento), já que algumas pessoas parecem pensar que isso acontece quando a função de fechamento retorna. Um exemplo diferente é necessário para mostrar que o fechamento é criado quando uma função é chamada .
precisa saber é o seguinte
89

Um fechamento é o local onde 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 sobre os fechamentos.

Rakesh Pai
fonte
35
Isso é apenas metade da explicação. O importante a ser observado sobre os fechamentos é que, se a função interna ainda estiver sendo referida após a saída da função externa, os valores antigos da função externa ainda estarão disponíveis para a interna.
pcorcoran 21/09/08
22
Na verdade, não são os valores antigos da função externa que estão disponíveis para a função interna, mas as variáveis antigas , que podem ter novos valores se alguma função puder alterá-los.
precisa saber é o seguinte
86

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
Gerardo Lima
fonte
82

Você está dormindo e convida 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.");
StewShack
fonte
80

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

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

Digite a descrição da imagem aqui

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

O processo consiste em duas etapas:

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

Digite a descrição da imagem aqui

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

Digite a descrição da imagem aqui

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

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.

Digite a descrição da imagem aqui

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

Encerramentos

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

Digite a descrição da imagem aqui

Como marcar LexicalEnvironments:

Digite a descrição da imagem aqui

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

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

O objetivo é garantir que, se a função interna quiser acessar uma variável externa no futuro, ela será 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 concluída.
  3. O navegador mantém o LexicalEnvironment e todas as suas propriedades (variáveis) na memória até que haja uma função interna que faça referência a ele.

Isso é chamado de encerramento.

Arvand
fonte
78

As funções JavaScript podem acessar:

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

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

Observe que 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 ativos. Na subcasa das funções externas / internas, uma função externa pode criar dados locais e, eventualmente, sair, e, no entanto, se alguma função interna sobreviver após a saída da função externa, as funções internas manterão os dados locais da função externa vivo.

Exemplo de um fechamento que usa o ambiente global:

Imagine que os eventos de botão Votação para cima e Votação para pilha excedente sejam implementados como closures, voteUp_click e voteDown_click, que têm acesso a variáveis ​​externas isVotedUp e isVotedDown, definidas globalmente. (Por uma questão de simplicidade, estou me referindo aos botões de votação de perguntas do StackOverflow, não à matriz de botões de votação de resposta.)

Quando o usuário clica no botão VoteUp, a função voteUp_click verifica se isVotedDown == true para determinar se é necessário votar ou simplesmente cancelar uma votação para baixo. A função voteUp_click é um fechamento 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 todos acessam seu ambiente.

John Pick
fonte
59

Como pai de uma criança de 6 anos, atualmente ensina crianças pequenas (e um novato em relação à codificação sem educação formal, para que sejam necessárias correções), 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 ela tem idade suficiente para tentar. Eu sugiro colar o código no 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 até apenas descrições de coisas. Você não pode tocar, cheirar ou provar. Você pode escrever, falar e ouvir. Você poderia usá-lo para criar cheiro e sabor toque 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, francês ou chinês são idiomas. Existem muitos idiomas que são entendidos por computadores e outros processadores eletrônicos. Para que o JavaScript seja entendido por um computador, é necessário um intérprete. Imagine se um professor que só fala russo vem ensinar sua classe 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 "todos se sentam" - assim todos vocês. A turma é 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, usa um navegador. Exemplos que você pode conhecer 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 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, pode haver resmas de tecido e bobinas de fio entrando e camisetas e jeans saindo. Nossa fábrica JavaScript processa apenas dados, não pode costurar, perfurar ou derreter metais. Em nossa fábrica de JavaScript, os dados entram e saem.

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

Como não tenho tempo para fazer compras, 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 pode 'conversar' com a geladeira e a função pode controlar o robô.

Uma função normalmente tem um nome, parênteses e chaves. Como isso:

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

Observe que /*...*/e //pare o código que está sendo lido pelo navegador.

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

PAIS: "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 à fábrica. Às vezes, a caixa postal pode ser marcada, por exemplo cookMeal(you, me, yourFriend, myFriend, fridge, dinnerTime) , nesse caso, você sabe quais dados você precisa fornecer.

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

O EXEMPLO DE CÓDIGO LONGO ACIMA

Nosso código começa com a palavra função , então sabemos que é uma! Então o nome da função canta - essa é a 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, existe uma chave assim {. Isso marca o início da função sing () . Tem um parceiro que marca o final de cantar () como este}

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

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

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

var person="an old lady";

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

VARIÁVEL GLOBAL: pessoa é uma variável global, o que significa que se você alterar seu valor de "uma velha senhora" para "um jovem", a pessoa continuará sendo jovem até que você decida alterá-la novamente e que qualquer outra função em o código pode ver que é um jovem. Pressione o F12botão ou veja 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 "pessoa" novamente para ver se 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 uma pessoa !"

Quando o navegador carrega o código JavaScript e atinge essa linha, ele inicia a função. Coloquei 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 principal função é cantar. Ele contém uma variável chamada firstPart que se aplica ao canto da pessoa que se aplica a cada um dos versos da música: "Havia" + pessoa + "que engoliu". Se você digitar firstPart no console, não receberá resposta porque a variável está bloqueada em uma função - o navegador não pode ver dentro das janelas coloridas dos aparelhos.

FECHAMENTOS: 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 pelas janelas coloridas.

Após os fechamentos vêm as linhas

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

A função sing () chama cada uma dessas funções na ordem em que são dadas. Em seguida, o trabalho da função sing () será concluído.

gratos
fonte
56

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

Imagine - você está brincando com seus irmãos e irmãs pequenos em toda a casa, e está andando com seus brinquedos e trouxe alguns deles para o quarto do seu irmão mais velho. Depois de um tempo, seu irmão voltou da escola e foi para o quarto dele, e ele trancou dentro dela, então agora você não podia mais acessar os brinquedos deixados lá de maneira direta. Mas você pode bater na porta e pedir ao seu irmão esses brinquedos. Isso é chamado de fechamento do brinquedo ; seu irmão fez isso por você, e agora ele está no escopo externo .

Compare com uma situação em que uma porta foi trancada por correntes de ar e ninguém dentro (execução da função geral); em seguida, algum incêndio local ocorre e incendeia a sala (coletor de lixo: D); em seguida, uma nova sala foi construída e agora você pode sair outro brinquedos lá (nova instância de função), mas nunca obtenha 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 pelo irmão e não importa se a sala está trancada. Aqui está um jsbin para brincar com ele.

dmi3y
fonte
49

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

Funções podem retornar dados. Um tipo de dados 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 dele e ver as variáveis ​​que ele usou, exceto a função que retornou. Essa nova função tem uma capacidade especial de olhar para trás na função que a criou e ver os dados nela.

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

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

Outra maneira realmente simples de explicar é 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.

Stupid Stupid
fonte
49

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. Toda função em JavaScript pode ser considerada um fechamento.

Encerramentos 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 o idioma não fornece meios para acessar variáveis ​​capturadas.

Como todas essas variáveis ​​externas referenciadas por uma função lexicamente aninhada são na verdade variáveis ​​locais na cadeia de suas funções anexas lexicamente (variáveis ​​globais podem ser consideradas variáveis ​​locais de alguma função raiz), e toda execução de uma função cria novas instâncias de suas variáveis ​​locais, segue-se que toda execução de uma função retornando (ou transferindo-a, como registrando-a como um retorno de chamada) uma função aninhada cria um novo fechamento (com seu próprio conjunto potencialmente exclusivo de variáveis ​​não-locais referenciadas que representam sua execução contexto).

Além disso, deve-se entender que variáveis ​​locais em JavaScript são criadas não no quadro da pilha, mas na pilha e destruídas apenas quando ninguém as está referenciando. Quando uma função retorna, as referências a suas variáveis ​​locais são decrementadas, mas ainda podem ser nulas se, durante a execução atual, se tornarem parte de um fechamento e ainda são referenciadas por suas funções lexicamente aninhadas (o que pode acontecer apenas 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();
srgstm
fonte
47

Talvez um pouco além de todos, exceto o mais precoce dos seis anos, mas alguns exemplos que ajudaram a tornar o conceito de fechamento em JavaScript um clique para mim.

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

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

outerFunction();

ALERTA: macaco

No exemplo acima, chama-se outerFunction, que por sua vez chama innerFunction. Observe como outerVar está disponível para innerFunction, evidenciado pelo alerta correto do valor de outerVar.

Agora considere o seguinte:

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

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

ALERTA: macaco

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

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

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

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

outerFunction = null;
alert(referenceToInnerFunction());

ALERTA: macaco ALERTA: macaco

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

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

//////////

Duas outras coisas sobre fechamentos a serem observadas. Primeiro, o fechamento sempre terá acesso aos últimos valores de sua função de contenção.

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

    innerFunction();
}

outerFunction();

ALERTA: gorila

Segundo, quando um fechamento é criado, ele mantém uma referência a todas as variáveis ​​e funções de sua função envolvente; não consegue escolher. E, portanto, os fechamentos devem ser usados ​​com moderação ou pelo menos com cuidado, pois podem consumir muita memória; muitas variáveis ​​podem ser mantidas na memória muito tempo após a conclusão da execução de uma função de contenção.

Michael Dziedzic
fonte
45

Eu simplesmente apontaria para a página Mozilla Closures . É a melhor, mais concisa e simples explicação dos princípios básicos do fechamento e do uso prático que eu encontrei. É altamente recomendado para quem aprende JavaScript.

E sim, eu recomendaria até 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.

mjmoody383
fonte
Eu concordo: a página Mozilla mencionada é particularmente simples e concisa. Surpreendentemente, sua postagem não foi tão apreciada quanto as outras.
Brice Coustillas