2011-02-04 24 views
11

Perché il seguente codice perde?Perché questa perdita in Internet Explorer 8?

for (var i = 0; i < 100; i++) { 
    var item = {}; 
    item.elem = document.createElement('div'); 
    document.body.appendChild(item.elem); 
    item.addEvent = function(name,listener) { 
     var self = this; 
     var wrappedListener = function() { 
      return listener.apply(self,arguments); 
     } 
     //Uh-oh creating a circular reference here! 
     //The wrappedListener has a closure on self and therefore on item.elem. 
     addEvent(this.elem,name,wrappedListener); 
     return wrappedListener; 
    } 
    var wrap = item.addEvent('eventName',listen); 

    //Now remove the eventHandler - this should free up the circular reference. 
    removeEvent(item.elem, 'eventName', wrap); 
    if (item.elem.parentNode) { 
     item.elem.parentNode.removeChild(item.elem); 
    } 
    //item.elem = null; //With this also un-commented, the leak disappears. 
    //The fact that I have to null item.elem tells me that something is holding 
    //a reference to item, and therefore elem. Setting elem to null fixes the 
    //problem, but since I am removing the event handler, I don't think this 
    //should be required. 
} 

Nota: addEvent e removeEvent sono solo astratti attachEvent/addEventListener differenze tra Internet Explorer e altri browser.

Ho creato un progetto jsFiddle che dimostra il problema. Basta avviare Internet Explorer 8 e guardarlo andare in Task Manager o Process Explorer. Inoltre, vedrai la definizione di addEvent e removeEvent lì.

http://jsfiddle.net/rJ8x5/34/

EDIT: Beh, mi si avvicinò con la seguente soluzione. Non è bello, ma funziona! http://jsfiddle.net/rJ8x5/43/

var item = {}; 
item.elem = document.createElement('div'); 
document.body.appendChild(item.elem); 
item.addEvent = function(name,listener) { 
    var wrappedListener = function() { 
     //Access the scope through the callee properties. 
     return listener.apply(arguments.callee.scope, arguments); 
    } 
    addEvent(this.elem,name,wrappedListener); 
    //Save the scope not as a closure, but as a property on the handler. 
    wrappedListener.scope = this 
    return wrappedListener; 
} 
var wrap = item.addEvent('eventName',listen); 
removeEvent(item.elem, 'eventName', wrap); 
//Force the circular reference to GO AWAY. 
wrap.scope = null 
if (item.elem.parentNode) { 
    item.elem.parentNode.removeChild(item.elem); 
} 
//item.elem = null; //No longer needed. 
+0

So che IE aveva un bug in cui utilizzava garbage collection separata per oggetti nativi e oggetti javascript, quindi ogni volta che si crea un ciclo tra oggetti javascript e oggetti nativi, nessuno dei garbage collector può ripulirlo. Potrebbe essere? Sto pensando che l'oggetto punta a item.elem che è un div che ha gestori che fanno riferimento all'elemento. – btilly

+0

- Non proprio pertinente alla tua domanda, ma puoi eliminare la variabile 'self' usando' listener.apply (item, arguments) '. :-) –

+0

Totalmente! Ma questa è in realtà una versione molto semplificata di una classe che ho scritto dove il metodo addEvent non ha accesso all'istanza in modo conveniente - quindi var self = questo – jordancpaul

risposta

5

Il problema sono gli eventi (come quasi sempre in Internet Explorer, btw).

Vedere http://jsfiddle.net/rJ8x5/39/ e notare come raccoglie i rifiuti.

Si stanno creando riferimenti circolari quando si allegano gli eventi. Maggiori informazioni a riguardo in Circular references to DOM objects on an HTML page cause a memory leak.

+0

Ah! Sì, perché la funzione intrappola un riferimento all'elemento DOM nella chiusura, e così quando è spazzatura raccolta da JScript l'elemento DOM perde. È giusto? – Pointy

+0

@Pointy: Sì, si tratta interamente dell'ambito del gestore di eventi, o piuttosto dei riferimenti DOM intrappolati in tale ambito :) –

+0

Sono a conoscenza delle perdite di memoria relative ai riferimenti circolari di IE. Il punto qui, tuttavia, è che sono specificamente _removing_ il gestore di eventi. Questo non dovrebbe rompere il riferimento circolare? – jordancpaul

1

Il codice perde perché si chiama attachEvent in modo errato - Microsoft documentation insiste sul fatto che il nome dell'evento sia un evento DHTML standard.

Se si modifica 'eventName' in 'click', non si verificano perdite.

Una soluzione più robusta sarebbe quella di modificare il codice di collegamento degli eventi per controllare 'on'+eventname in domElement e rifiutare di allegare l'evento se questo è falso.

Problemi correlati