O que é tão útil em fechamentos (em JS)? [duplicado]

4

Na minha busca para entender os encerramentos no contexto do JS, eu me pergunto por que você precisa usar fechamentos? O que é tão bom sobre ter uma função interna capaz de acessar as variáveis da função pai mesmo após a função pai retornar? Não tenho certeza se fiz a pergunta corretamente porque não entendo como usá-las.

Alguém pode dar um exemplo do mundo real em JS, onde um fechamento é mais benéfico versus a alternativa, o que quer que seja?

Editar: a pergunta Por que o fechamento é importante para JavaScript? não respondi com clareza suficiente o que eu quero entender. Então, não considero isso uma duplicata.

Vou dar uma olhada em todas as respostas / recursos, e vou marcar um aceito. Obrigado a todos!

    
por Mark Bubel 03.07.2013 / 05:23
fonte

4 respostas

16

O Javascript é assíncrono por natureza. Seu código não pode esperar. Em vez disso, você diz "quando isso acontecer, chame essa função". Isso significa que o código Javascript está cheio de funções. Funções que são passadas para outras funções, chamadas por elas, e às vezes até retornadas por elas para serem chamadas posteriormente. Essas funções geralmente desejam usar alguns dados que seu escopo de definição tem disponível, mas o chamador (o navegador) não.

function getSize(src, callback){
  var img = new Image();
  img.onload = function(event){
     console.log(event);
     callback(img.width, img.height);
  }
  img.src = src
}

Agora, isso parece um código normal. Exceto que usa o escopo de fechamento! img e callback são ambos fechados por img.onload . Agora, imagine que você não tenha encerramentos. quais são as outras opções? Você pode encontrar img no objeto de evento, mas definitivamente não é o callback . Você precisa de uma função que aceita dois tipos de argumentos: aqueles que são definidos quando você define uma função e aqueles que são definidos quando você a chama. Ou, você pode dizer ao sistema tudo que você precisa. Ou você precisa de um mecanismo nativo de curry *. Você não quer dizer ao código estrangeiro tudo o que sabe apenas porque precisa reagir a seus eventos, então vamos supor que tal mecanismo exista. ES5 apresenta bind :

* currying é uma técnica em que uma nova função é criada ligando argumentos a uma função existente antecipadamente e enviando os argumentos como os argumentos restantes para a função original. O valor de retorno da função original é então retornado

function getSize(src, callback){
  var img = new Image();
  img.onload = function(callback, img, event){
     callback(img.width, img.height);
  }.bind(null, callback, img); //not needed if you have closures
  img.src = src
}

Quais são os argumentos para bind ? null define o contexto this . Nosso método curry hipotético não. Então, callback e img . Estes devem estar na mesma ordem que os argumentos para img.onload . Isso é aceitável de vez em quando, mas não se você tiver que se curvar para cima em ambas as direções. Mais importante, é aceitável se você tiver três argumentos, mas não se tiver trinta.

Agora, para a parte engraçada: O escopo global também é tecnicamente um escopo de fechamento. Mas, mesmo que não seja, você ainda não deseja definir as funções de utilidade do seu módulo no escopo global (problemas de namespace). Funções são armazenadas em variáveis!

getSize = function(Image, src, callback){
  var img = new Image();
  img.onload = function(callback, img, event){
     callback(img.width, img.height);
  }.bind(null, callback, img); //not needed if you have closures
  img.src = src
}.bind(null, Image) //not needed if you have closures

Agora, suponha que você queira transformar as coordenadas da imagem por algum motivo e essa transformação depende da origem. Onde precisamos adicionar transform e src ?

getSize = function(Image, transform, src, callback){ //here
  var img = new Image();
  img.onload = function(transform, src, callback, img, event){ //here
     var coord = transform(img.width, img.height, src); //used here
     callback(coord.x, coord.y);
  }.bind(null, transform, src, callback, img); //here
  img.src = src;
}.bind(null, transform, Image); //here

Você consegue identificar o bug? Sim, os argumentos da ligação externa estão na ordem errada.

Você pode contornar isso mantendo todos os parâmetros em um objeto global. Naturalmente, você não deseja modificar o escopo do chamador, portanto, você define seu próprio escopo sempre que quiser passar alguns argumentos extras. Com algumas boas convenções, isso pode ser razoavelmente livre de bugs:

var scope = {Image: Image, ...}

getSize = function(scope, src, callback){
  var img = new scope.Image();
  var scope = {scope: scope, src: src, callback: callback, img:img};
  img.onload = function(scope, event){
     var coord = scope.transform(scope.img.width, scope.img.height, scope.src);
     callback(coord.x, coord.y);
  }.bind(null, scope);
  img.src = src;
}.bind(null, scope)

Você consegue identificar o bug agora? scope.transform é undefined . Precisamos de scope.scope.transform Há outro. Você pode identificá-lo também? Agora isso está ficando ridículo, mesmo se usássemos uma variável de uma letra para scope aqui. Talvez pudéssemos utilizar a cadeia de protótipos:

var scope = {Image: Image, ...}

getSize = function(scope, src, callback){
  var img = new scope.Image();
  var scope = scope.Object.create(scope)
  scope.src = src;
  scope.callback = callback;
  scope.img = img;
  img.onload = function(scope, event){
     var coord = scope.transform(scope.img.width, scope.img.height, scope.src);
     callback(coord.x, coord.y);
  }.bind(null, scope);
  img.src = src;
}.bind(null, scope)

é claro, poderíamos definir nossas variáveis locais diretamente no escopo, para que não precisássemos declará-las duas vezes se alguma função interna pudesse usá-las. Infelizmente, não há tal truque para os argumentos. Existe a variável arguments , mas usá-la para nosso propósito é um pouco confusa ( scope.getSizeArgs[0] ; quick - qual é?). No entanto, até agora, para contornar a falta de encerramentos, temos:

  1. para cada escopo, criou uma variável scope especial, com o escopo pai como seu protótipo.
  2. adicionou todos os argumentos a ele
  3. usado para todas as variáveis locais que podem ser necessárias em uma função interna.

Claro, isso está ficando ridículo. Talvez se a linguagem de alguma forma soubesse que, se usarmos uma variável não declarada, ela deve procurar por ela no escopo de definição, e até mesmo criar esse objeto scope para nós quando necessário, para que as variáveis possam sobreviver às suas funções definidoras? Bem, isso acontece.

function getSize(src, callback){
  var img = new Image();
  img.onload = function(event);
     console.log(event);
     var coords = transform(img.width, img.height);
     callback(coords.x, coords.y);
  }
  img.src = src;
}

Além disso, essa passagem explícita tem outra desvantagem: sempre que você escreve no escopo, sempre grava em uma variável local. Você não pode modificar uma variável de uma função interna. Claro, você poderia usar arrays de um elemento ou um tipo especial Reference<T> , mas isso cheira a uma linguagem não impressionante. Com fechamentos, você pode fazer isso diretamente. Você realmente precisa disso?

var width = window.clientWidth;
window.addEventListener('resize', function(){width = window.clientWidth});
setInterval(function(){
  ...

Você pode dizer que às vezes você quer o escopo do chamador como a alternativa seria ter 30 argumentos. Mas o chamador não quer que você veja o escopo local, e eles não querem ter que evitar nomes de variáveis que você pode considerar significativos. Você ainda pode passar objetos por aí, mas não os chama de escopos. Alguns podem dizer "objeto de transferência de dados"; Eu prefiro o termo "argumentos nomeados"

$.ajax({
  url: "http://www.example.com/cors-api/echo.json",
  dataType: "json",
  success: function(response){
    ..

Agora, para o exemplo clássico de escopo de fechamento, conclua com uma expressão de função imediatamente invocada para limitar o escopo da variável estática nextId :

var getId = (function(){
  var nextId = 0;
  return function getId(){ //named for clarity
    return nextId++;
  }
})()

getId() //0
getId() //1
getId() //2
    
por 03.07.2013 / 07:08
fonte
5

A coisa realmente útil sobre encerramentos é que eles permitem que você coloque dados arbitrários em um retorno de chamada, independente da assinatura da função.

Um bom exemplo do mundo real de onde é útil é a animação. JS não é meu idioma mais strong, então nem vou tentar escrever código para isso, mas aqui está a ideia geral:

Digamos que você tenha uma função chamada Timer , que aceita dois parâmetros, um intervalo e um retorno de chamada. (Eu sei que o DOM tem algo parecido com isso, que é usado para implementar animações em JavaScript.) E após o intervalo, ele chama seu retorno de chamada assim: CallbackFunction();

Veja o problema óbvio? Ele não fornece dados que possam ser necessários dentro de sua função de retorno de chamada. Alguns sistemas de retorno de chamada permitem que você passe um objeto arbitrário ao configurá-lo e, em seguida, quando o retorno de chamada é chamado, ele passa esse objeto de volta como contexto. Mas então as coisas ficam complicadas se você precisar de dois pedaços de dados dentro do retorno de chamada, e assim por diante ...

A solução é passar em um fechamento e aproximá-lo de todos os dados de que você precisa. Em seguida, ele pode ser chamado como um retorno de chamada posteriormente (após o cronômetro ter sido executado, por exemplo) e pode manter todos os dados (variáveis incluídas) necessários para executar seu trabalho.

    
por 03.07.2013 / 05:39
fonte
0

Eu gostaria de dar uma cena para closures:

 function f1() {
      var n=999;

      setter=function() { n += 1 }
      function getter() {
           alert(n);
      }

      return f2;
 }

Nesta função, a variável n se torna private e possui métodos getter e setter (como Java).

    
por 03.07.2013 / 08:55
fonte
0

Este exemplo é bem parecido com o gravityplusplus '. O código assíncrono é o caso mais comum, como assinalaram Mason e Jan; mas também é útil para módulos localizados (módulo que significa "um bloco de código que faz sua própria tarefa individual). Aqui está algum código todo no mesmo escopo (significando, isto é como NÃO fazê-lo) .

counter = 0;

function incrementCounter() {
    counter += 1;
    return counter;
}

// Define Ryu's attack functions here
function lightPunch() {
    //TODO
}
function counter() {
    //TODO
}

function getCounterValue() {
    return counter;
}

O que getCounterValue() vai retornar? Eu nem tenho certeza; Eu acho que vai ser a função que eu declarei, não o número. Se estes foram separados em fechamentos de alguma forma (e existem várias maneiras de fazer isso; a gravidade listou um bom exemplo), então não há chance de os nomes das variáveis serem misturados. E em um grande projeto corporativo com páginas que consistem em mais de 50 arquivos JS, haverá MUITOS nomes de variáveis.

    
por 03.07.2013 / 15:26
fonte