43

Quali sono alcuni dei problemi standard o modelli di codifica in jQuery che portano a perdite di memoria?jQuery modelli di perdita di memoria e cause


ho visto una serie di domande relative alla chiamata ajax() o jsonp o la rimozione DOM su StackOverflow. La maggior parte delle domande di perdita di memoria jQuery sono focalizzate su problemi specifici o browser e sarebbe bello avere un elenco dei modelli di perdita di memoria standard in jQuery.

Ecco alcune domande correlate su SO:

Risorse sul web:

risposta

28

Da quanto ho capito, gestione della memoria in JavaScript viene realizzata mediante conteggio riferimento - mentre un riferimento a un oggetto esiste ancora, non sarà deallocato. Ciò significa che la creazione di una perdita di memoria in un'applicazione a singola pagina è banale e può far scattare quelli di utilizzo provenienti da uno sfondo java. Questo non è specifico per JQuery. Prendi ad esempio il seguente codice:

function MyObject = function(){ 
    var _this = this; 
    this.count = 0; 
    this.getAndIncrement = function(){ 
     _this.count++; 
     return _this.count; 
    } 
} 

for(var i = 0; i < 10000; i++){ 
    var obj = new MyObject(); 
    obj.getAndIncrement(); 
} 

Sembrerà normale fino a quando non guardi l'utilizzo della memoria. Le istanze di MyObject non vengono mai deallocate mentre la pagina è attiva, a causa del puntatore "_questo" (aumentare il valore massimo di i per vederlo in modo più drammatico). (Nelle versioni precedenti di IE non erano mai stati rilasciati fino alla chiusura del programma.) Dal momento che gli oggetti javascript possono essere condivisi tra i frame (non è consigliabile provarli poiché è estremamente problematico.), Ci sono casi in cui anche in un browser moderno javascript gli oggetti possono durare molto più a lungo di quanto dovrebbero.

Nel contesto di jQuery, i riferimenti sono spesso conservati per salvare l'overhead di ricerca dom - per esempio:

function run(){ 
    var domObjects = $(".myClass"); 
    domObjects.click(function(){ 
     domObjects.addClass(".myOtherClass"); 
    }); 
} 

Questo codice mantenere il domObject (e tutto il suo contenuto) per sempre, a causa della riferimento ad esso nella funzione di callback.

Se gli scrittori di jquery hanno saltato istanze come questa internamente, allora la libreria stessa perde, ma più spesso è il codice client.

Il secondo esempio può essere corretto pulendo esplicitamente il puntatore quando non è richiesto:

function run(){ 
    var domObjects = $(".myClass"); 
    domObjects.click(function(){ 
     if(domObjects){ 
      domObjects.addClass(".myOtherClass"); 
      domObjects = null; 
     } 
    }); 
} 

o rifare la ricerca:

function run(){ 
    $(".myClass").click(function(){ 
     $(".myClass").addClass(".myOtherClass"); 
    }); 
} 

Una buona regola è di essere attento dove si definiscono le funzioni di callback ed evitare troppi nidi ove possibile.

Edit: Come è stato sottolineato nei commenti di Erik, si potrebbe anche usare il puntatore this per evitare il unnescessary dom ricerca:

function run(){ 
    $(".myClass").click(function(){ 
     $(this).addClass(".myOtherClass"); 
    }); 
} 
+2

Ecco un esempio: http://jsfiddle.net/qTu6y/8/ Puoi utilizzare Chrome "Take a heapshot" nel profiler per vedere che ogni esecuzione di quel blocco di codice consuma circa 20 MB di RAM. (Durante il test ho colpito lo "Script usato troppo errore RAM su chrome") – Raynos

+0

+1 per "quelli di noi provenienti da uno sfondo Java" ... e per la spiegazione lucida. – Thimmayya

+1

$ (this) .addClass sarebbe meglio per le prestazioni nell'ultimo caso poiché 'this' rappresenta una raccolta di elementi JS DOM core (che è ciò che gli oggetti JQuery tipicamente stanno eseguendo wrapping usando un modello stile adattatore) e JQ non dovrà accedi nuovamente al DOM o analizza ogni singolo elemento DOM sulla pagina nel caso di

15

io contribuisco un anti-modello qui, che è la perdita "riferimento a catena media".

Uno dei punti di forza di jQuery è la sua API concatenamento, che consente di continuare a cambiare, filtrare e manipolare gli elementi:

$(".message").addClass("unread").find(".author").addClass("noob"); 

Alla fine di quella catena di avere un oggetto jQuery con tutti i". messaggio .author ", ma l'oggetto fa riferimento a e oggetto con gli elementi originali" .message ". È possibile ottenere a loro tramite il metodo .end() e fare qualcosa per loro:

$(".message") 
    .find(".author") 
    .addClass("prolific") 
    .end() 
    .addClass("unread"); 

Ora, quando viene utilizzato in questo modo non ci sono problemi con le perdite. Tuttavia, se si assegna il risultato di una catena a una variabile che ha una lunga durata, i riferimenti posteriori ai set precedenti rimangono intorno e non possono essere raccolti prima che la variabile vada fuori ambito. Se quella variabile è globale, i riferimenti non escono mai dall'ambito. Ad esempio, supponiamo di aver letto su alcuni post del blog del 2008 che $("a").find("b") era "più efficiente" di $("a b") (anche se non è nemmeno degno di pensare a una simile micro-ottimizzazione). Si decide avete bisogno di una pagina a livello globale per tenere una lista degli autori in modo da fare questo:

authors = $(".message").find(".author"); 

Ora si dispone di un oggetto jQuery con la lista degli autori, ma si riferisce anche di nuovo ad un oggetto jQuery che è l'elenco completo dei messaggi. Probabilmente non lo userai mai e nemmeno saprai che è lì, e sta assorbendo memoria.

noti che perdite possono avvenire solo con i metodi che selezionano nuovi elementi da un insieme esistente, come .find, .filter, .children ecc I documenti indicano quando si restituisce un insieme nuovo. Semplicemente utilizzando un API concatenamento non causare una perdita se la catena ha metodi non-filtraggio semplici come .css, quindi questo è bene:

authors = $(".message .author").addClass("prolific");