2011-02-04 12 views
11

¿Por qué se filtra el siguiente código?¿Por qué se produce esta fuga en 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 y removeEvent son sólo para abstraer attachEvent/addEventListener diferencias entre Internet Explorer y otros navegadores.

Creé un proyecto jsFiddle que demuestra el problema. Simplemente inicie Internet Explorer 8 y mire cómo va en Task Manager o Process Explorer. Además, verá la definición de addEvent y removeEvent allí.

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

EDIT: Bueno, se me ocurrió con la siguiente solución. No es bonito, pero funciona! 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

Sé que IE solía tener un error en el que utilizaría recolección de elementos no utilizados para objetos nativos y objetos JavaScript, por lo que cada vez que se forma un ciclo entre objetos JavaScript y objetos nativos, ni el recolector de basura podría limpiarlo. ¿Podría ser eso? Estoy pensando que el elemento apunta a item.elem que es un div que tiene manejadores que se refieren al artículo. – btilly

+0

- No es realmente pertinente a su pregunta, pero puede eliminar la variable 'self' utilizando en su lugar 'listener.apply (item, arguments)'. :-) –

+0

¡Totalmente! Pero esta es una versión muy simplificada de una clase que escribí donde el método addEvent no tiene acceso a la instancia tan convenientemente - por lo tanto, var self = this – jordancpaul

Respuesta

5

El problema es que los eventos (como casi siempre en Internet Explorer, por cierto).

Mire http://jsfiddle.net/rJ8x5/39/ y observe cómo la basura se acumula bien.

Está creando referencias circulares cuando adjunta los eventos. Obtenga más información al respecto en Circular references to DOM objects on an HTML page cause a memory leak.

+0

¡Ah! Sí, porque la función atrapa una referencia al elemento DOM en el cierre, por lo que cuando es basura recolectada por JScript, el elemento DOM se filtra. ¿Está bien? – Pointy

+0

@Pointy: Sí, se trata de determinar el alcance del controlador de eventos, o más bien acerca de las referencias DOM atrapadas en ese ámbito :) –

+0

Soy consciente de las fugas de memoria relacionadas con la referencia circular de IE. El punto aquí, sin embargo, es el que estoy específicamente retirando el controlador de eventos. ¿No debería esto romper la referencia circular? – jordancpaul

1

El código se filtra porque está llamando a attachEvent incorrectamente - Microsoft documentation insiste en que el nombre del evento sea un evento DHTML estándar.

Si cambia 'eventName' a 'click', no tiene fugas.

Una solución más robusta sería cambiar el código de suceso de evento para verificar 'on'+eventname in domElement y negarse a adjuntar el evento si esto es falso.

Cuestiones relacionadas