2013-06-04 18 views
6

consideri l'esempio di metodo piuttosto standard in angolare J, che aggiorna la vista:Questo modello AJAX è una perdita di memoria?

$scope.fetchResults = function() { 
    // Some local variable that will cause creation of closure 
    var hugeData = serviceX.getMilionRecords(); 

    // Any call to any resource with success and error handlers. 
    $http({ 
     method: "GET", 
     url: "/rest-api/bulk-operation-x", 
     params: { someParam: hugeData.length } 

    }).success(function() { 
     var length = hugeData.length; 
     $scope.reportToUser("Success, that was " + length + " records being processed!"; 

    }).error(function() { 
     var length = hugeData.length; 
     $scope.reportToUser("Something went wrong while processing " + length + " records... :-("; 
    }); 
}; 

Questo è ovviamente esempio ipotetico, ma mostra ben reticolo, che può essere descritta come riutilizzo delle variabili locali dall'interno Callback AJAX.

Naturalmente in entrambi gestori (success e error) stiamo creando una chiusura sopra hugeData che è direttamente riferimento da gestori di callback.

La mia domanda è: poiché il risultato della chiamata AJAX può essere solo positivo o negativo, il riutilizzo di questo codice causerà la perdita di memoria nel tempo? Risponderei "si", ma non potrei provarlo in modo affidabile nei miei test locali.

Vorrei un guru più esperto per spiegarmelo. Mi piacerebbe ricevere risposta da chiunque lavori con Angular quotidianamente, ma sono gradite anche le risposte jquery.

+2

"Perdita di memoria" è un termine molto specifico che si riferisce alla memoria allocata e successivamente mai liberata. Si applica solo ai contesti in cui la gestione della memoria viene eseguita manualmente. Dato che nella gestione di JS è trasparente per il programmatore, le perdite di memoria sono rilevanti solo se si parla di schemi di codifica che causano perdite di memoria in determinate circostanze, come nel caso delle versioni precedenti di IE. Non sono sicuro che la domanda abbia senso come scritto. – Jon

+0

Non sono d'accordo con il commento di @ Jon. Una cattiva pratica può causare perdite di memoria, come riempire gli oggetti nello scope globale, ecc. In questo caso non lo sarà, poiché l'enorme variabileData viene cancellata non appena una delle callback è terminata (e l'istanza $ http viene ripulita in alto) – rewritten

+0

@Jon, è possibile creare facilmente una perdita di memoria in qualsiasi lingua con gestione della memoria trasparente. Java, Scala, JavaScript, C#, lo chiami. @rewritten, questo è esattamente quello di cui sto parlando. Come sai che 'hugeData' viene cancellato quando' success() 'finisce? Dal punto di vista della lingua, la chiusura usata in 'error()' potrebbe essere eseguita in un secondo momento in futuro. A meno che Angular non faccia qualcosa sotto il cofano (come annullare l'altra callback al termine), allora potremmo avere una perdita di memoria qui. –

risposta

4

si avrà una perdita di memoria non appena si restituisce il risultato di $http() chiamata (o qualsiasi oggetto o una funzione che ha accesso a hugeData) nel campo di applicazione esterna di fetchResults.

con il codice, nulla di grande è esposto direttamente al di fuori di fetchResults, e il risultato della chiamata $http() vivrà fino a quando non può avere esito positivo o negativo, quindi chiamando corrispondente richiamata, di arrivare finalmente GC'ed.

Vedi per approfondimenti: http://jibbering.com/faq/notes/closures/#clIdRes

Come @ ŁukaszBachman osserva, questo non garantisce che non ci siano perdite di memoria. Qualsiasi riferimento ciondolante al tuo grande oggetto o alla tua callback con oggetto di grandi dimensioni in ambito, causerà dolore.

Quindi, passiamo all'applicazione $q ($http basata su $q).

Se si seleziona https://github.com/angular/angular.js/blob/master/src/ng/q.js#L191, si può vedere che il metodo resolve() delle prime copie anticipate l'elenco dei callback registrati in una variabile locale al metodo:

var callbacks = pending; 

annulla successivamente l'esterno pending (che è stato definito a livello defer)

pending = undefined; 

poi, al prossimo tick, esegue i callback. Le cose possono essere complicate dal fatto che l'argomento del callback può essere differito di per sé (aggiungendo un ulteriore ritardo all'esecuzione), ma al massimo si può entrare in un ciclo infinito. (E non è divertente!). Se sei abbastanza fortunato da non entrare nel ciclo, allora ad un certo punto l'array di callback è esaurito, e quindi non vi è alcun riferimento all'elenco dei callback, quindi è disponibile per GC.

Ma.

Le cose possono andare storte se le imponi.

È possibile utilizzare arguments.callee all'interno di una richiamata.

Puoi anche lanciare birra sulla tua tastiera.

Se salti fuori dalla finestra, a meno che non vivi al primo piano, probabilmente ti farai male.

Happy EcmaScripting!

+0

Perché sei sicuro che il risultato di $ http() chiamerà live fino a quando non avrà successo o fallirà? AFAIU la chiusura è gestita dal mio motore JS stesso, quindi dal mio esempio non puoi esserne sicuro, giusto? Dovresti controllare l'impl interno di '$ http' per verificare che la seconda callback venga eliminata e possa quindi essere liberata da GC. Sono corretto? (Ho aggiunto l'articolo fornito alla mia lista di lettura) –

+0

Controllare l'implementazione '$ q', in particolare il metodo' resolve' (https://github.com/angular/angular.js/blob/master/src/ng/ q.js # L191). La catena di callback 'in attesa 'viene completamente rimossa dall'ambito della promessa non appena la promessa viene risolta, e viene internalizzata in un'altra variabile nel metodo di risoluzione. Una volta completata la risoluzione, non rimangono più riferimenti ai callback, rendendoli disponibili per GC. – rewritten

+0

In altre parole, sei corretto. Ed è fatto esattamente in questo modo, l'elenco completo dei callback registrati viene ripulito nel processo di risoluzione. – rewritten