Cosa c'è di così utile nelle chiusure (in JS)? [duplicare]

4

Nella mia ricerca di comprendere chiusure nel contesto di JS, mi trovo a chiedere perché hai bisogno di usare le chiusure? Cosa c'è di così bello nell'avere una funzione interna in grado di accedere alle variabili della funzione genitore anche dopo il ritorno della funzione genitore? Non sono nemmeno sicuro di aver fatto correttamente la domanda perché non capisco come usarli.

Qualcuno può dare un esempio del mondo reale in JS dove una chiusura è più vantaggiosa rispetto all'alternativa, qualunque essa sia?

Modifica: la domanda Perché la chiusura è importante per JavaScript? non ha risposto abbastanza chiaramente ciò che voglio capire. Quindi, non lo considero un duplicato.

Darò un'occhiata a tutte le risposte / risorse e contrassegnerò una risposta accettata. Grazie a tutti!

    
posta Mark Bubel 03.07.2013 - 05:23
fonte

4 risposte

16

Javascript è asincrono per sua natura. Il tuo codice non può aspettare. Invece, tu dici "quando succede, chiama questa funzione". Ciò significa che il codice Javascript è pieno di funzioni. Funzioni che vengono passate ad altre funzioni, chiamate da loro e talvolta anche restituite da esse per essere chiamate in seguito. Queste funzioni spesso vogliono utilizzare alcuni dati che il loro ambito di definizione è disponibile, ma il loro chiamante (il browser) non lo fa.

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

Ora, sembra un codice normale. Tranne che utilizza l'ambito di chiusura! img e callback sono entrambi chiusi da img.onload . Ora, immagina di non avere chiusure. Quali sono le altre opzioni? Potresti trovare img nell'oggetto evento, ma sicuramente non callback . È necessaria una funzione che accetta due tipi di argomenti: quelli impostati quando si definisce una funzione e quelli impostati quando lo si chiama. Oppure, puoi dire al sistema tutto ciò di cui hai bisogno. Oppure, è necessario un meccanismo di curriculum nativo *. Non vuoi dire al codice straniero tutto ciò che sai solo perché devi reagire ai suoi eventi, quindi supponiamo che questo meccanismo esista. ES5 introduce bind :

* currying è una tecnica in cui una nuova funzione viene creata in anticipo vincolando gli argomenti a una funzione esistente e inviando gli argomenti come gli argomenti rimanenti alla funzione originale. Il valore di ritorno della funzione originale viene quindi restituito

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
}

Quali sono gli argomenti su bind ? null imposta il contesto this . Il nostro ipotetico metodo curry non lo farebbe. Quindi, callback e img . Questi devono essere nello stesso ordine degli argomenti a img.onload . Questo è accettabile ogni tanto, ma non se devi curry in salita in entrambe le direzioni. Ancora più importante, è accettabile se hai tre argomenti, ma non se ne hai trenta.

Ora, per la parte divertente: l'ambito globale è tecnicamente anche un ambito di chiusura. Ma, anche se non lo fosse, non si desidera ancora definire le funzioni di utilità del modulo nell'ambito globale (problemi di namespacing). Le funzioni sono memorizzate in variabili!

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

Ora, supponi di voler trasformare le coordinate dell'immagine per qualche motivo, e che la trasformazione dipende dalla fonte. Dove dobbiamo aggiungere 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

Riesci a individuare l'errore? Sì, gli argomenti di collegamento esterni sono nell'ordine sbagliato.

È possibile aggirare questo tenendo tutti i parametri in un oggetto globale. Naturalmente, non si desidera modificare l'ambito del chiamante, in modo da definire il proprio ambito ogni volta che si desidera passare alcuni argomenti aggiuntivi. Con alcune buone convenzioni, questo potrebbe essere ragionevolmente privo di errori:

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)

Puoi individuare il bug ora? scope.transform è undefined . Abbiamo bisogno di scope.scope.transform Ne esiste un altro. Puoi individuarlo anche tu? Ora questo sta diventando ridicolo, anche se qui abbiamo usato una variabile a una lettera per scope . Forse potremmo utilizzare la catena del prototipo:

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)

naturalmente, potremmo definire le nostre variabili locali direttamente nello scope in modo da non doverle dichiarare due volte se alcune funzioni interne potrebbero usarlo. Sfortunatamente, non c'è un tale trucco per gli argomenti. C'è la variabile arguments , ma usarla per il nostro scopo è un po 'caotica ( scope.getSizeArgs[0] ; quick - quale è?). Tuttavia, finora, per aggirare la mancanza di chiusure abbiamo:

  1. per ogni ambito, ha creato una variabile speciale scope , con l'ambito del genitore come prototipo.
  2. ha aggiunto tutti gli argomenti ad esso
  3. lo ha usato per tutte le variabili locali che potrebbero essere necessarie in una funzione interna.

Certo, sta diventando ridicolo. Forse se la lingua in qualche modo sapeva che se usassimo una variabile non dichiarata, dovrebbe cercarla nello scope di definizione, e persino creare tale oggetto scope per noi quando necessario, in modo che le variabili possano sopravvivere alle loro funzioni di definizione? Bene, lo fa.

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;
}

Inoltre, questo passaggio esplicito ha un altro svantaggio: ogni volta che si scrive sull'ambito, si scrive sempre su una variabile locale. Non è possibile modificare una variabile da una funzione interna. Certo, potresti utilizzare matrici a un elemento o un tipo speciale Reference<T> , ma questo ha un odore di un linguaggio non eccezionale. Con chiusure, puoi farlo direttamente. Ne hai davvero bisogno?

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

Si potrebbe dire che a volte si desidera l'ambito del chiamante in quanto l'alternativa sarebbe avere 30 argomenti. Ma il chiamante non vuole tu per vedere il loro ambito locale, e non vogliono dover evitare nomi di variabili che un giorno potresti considerare significativi. Puoi ancora passare oggetti, ma non li chiami scope. Alcuni potrebbero dire "oggetto di trasferimento dati"; Preferisco il termine "named arguments"

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

Ora, per il classico esempio di chiusura scope, completa con un'espressione di funzione immediatamente invocata per limitare l'ambito della variabile statica nextId :

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

getId() //0
getId() //1
getId() //2
    
risposta data 03.07.2013 - 07:08
fonte
5

La cosa veramente utile delle chiusure è che ti permettono di bloccare dati arbitrari in un callback, indipendentemente dalla firma della funzione.

Un buon esempio del mondo reale in cui è utile è l'animazione. JS non è il mio linguaggio più strong, quindi non cercherò nemmeno di scrivere codice per questo, ma ecco l'idea generale:

Supponiamo che tu abbia una funzione chiamata Timer che richiede due parametri, un intervallo e un callback. (So che il DOM ha qualcosa del genere, usato per implementare animazioni in JavaScript.) E dopo che l'intervallo è scaduto, chiama il callback in questo modo: CallbackFunction();

Vedi il problema ovvio? Non ti restituisce i dati che potrebbero essere necessari all'interno della tua funzione di callback. Alcuni sistemi di callback consentono di passare un oggetto arbitrario quando lo si imposta, e quindi quando viene richiamato il callback, restituisce quell'oggetto come contesto. Ma poi le cose diventano pelose se hai bisogno di due pezzi di dati all'interno del callback, e così via ...

La soluzione è passare una chiusura e chiuderla attorno a tutti i dati necessari. Quindi può essere richiamato come callback in un secondo momento (dopo che il timer è stato eseguito, ad esempio) e può conservare tutti i dati (variabili incluse) di cui ha bisogno per eseguire il suo lavoro.

    
risposta data 03.07.2013 - 05:39
fonte
0

Vorrei dare una scena per le chiusure:

 function f1() {
      var n=999;

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

      return f2;
 }

In questa funzione, la variabile n diventa private e ha metodi getter e setter (come Java).

    
risposta data 03.07.2013 - 08:55
fonte
0

Questo esempio è molto simile a gravityplusplus '. Il codice asincrono è il caso più comune, come hanno sottolineato Mason e Jan; ma è anche utile per i moduli localizzati (modulo che significa "un blocco di codice che svolge il proprio compito individuale.) Ecco alcuni codici tutti nello stesso ambito (ovvero, questo è come NON farlo) .

counter = 0;

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

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

function getCounterValue() {
    return counter;
}

Che cosa restituirà getCounterValue() ? Non ne sono nemmeno sicuro; Penso che sarà la funzione che ho dichiarato, non il numero. Se questi sono stati separati in chiusura in qualche modo (e ci sono diversi modi per farlo, la gravità indica un buon esempio) quindi non c'è possibilità che i nomi delle variabili vengano confusi. E in un grande progetto aziendale con pagine composte da oltre 50 file JS, ci saranno molti nomi di variabili.

    
risposta data 03.07.2013 - 15:26
fonte

Leggi altre domande sui tag