2010-08-03 12 views
15

Muchos artículos (por ejemplo, msdn) han dicho que una referencia circular no se puede limpiar en algunos navegadores cuando se trata de un objeto DOM y un objeto JS.JQuery Garbage Collection: ¿Esto será limpio?

(IE 6 no puede hacerlo en absoluto y que IE7 sólo puede hacerlo entre solicitudes de páginas):

Javascript nativo (Fugas):

function leak(){ 
    var elem = document.createElement("DIV"); 
    document.body.appendChild(elem); 
    elem.onclick = function() { 
     elem.innerHTML = elem.innerHTML + "."; 
     // ... 
    }; 
} 

Debido a la propiedad onload del elemento se refiere de nuevo a sí mismo a través de un cierre, se crea una referencia circular :

elem [DOM] -> elem.onclick [JS] -> elem [DOM]

jQuery Versión (no se escapa):

function leak(){ 
    var elem = $('<div></div>'); 
    $(document.body).append(elem); 
    elem.click(function() { 
     elem.html(elem.html() + "."); 
     // ... 
    }; 
} 

En este caso, jQuery se detiene la fuga suceda en todos los navegadores en cuestión a pesar de que todavía hay una referencia circular:

elem [JS] -> element [DOM] -> elem.onclick [JS] -> elem [JS]

Mi pregunta: ¿Cómo detiene jQuery la fuga si todavía hay una referencia circular?

Respuesta

6

La última cosa en el código jQuery (antes del código de chisporroteo, su motor de selección) es la siguiente (que es el código para prevenir las fugas):

// Prevent memory leaks in IE 
// Window isn't included so as not to unbind existing unload events 
// More info: 
// - http://isaacschlueter.com/2006/10/msie-memory-leaks/ 
if (window.attachEvent && !window.addEventListener) { 
    window.attachEvent("onunload", function() { 
     for (var id in jQuery.cache) { 
      if (jQuery.cache[ id ].handle) { 
       // Try/Catch is to handle iframes being unloaded, see #4280 
       try { 
        jQuery.event.remove(jQuery.cache[ id ].handle.elem ); 
       } catch(e) {} 
      } 
     } 
    }); 
} 

Al hacer nada en jQuery, almacena tanto lo que ha hecho (es decir, la función) como a qué (es decir, el elemento DOM). onunload pasa por el caché jQuery eliminando las funciones de los manejadores de eventos de su propio caché interno (que es donde los eventos se almacenan de todos modos en lugar de en los nodos DOM individuales).

Ah, y la línea:

if (window.attachEvent && !window.addEventListener) { 

asegura que sólo funciona en IE.

+0

Por lo tanto, elimina el elemento y rompe la referencia circular para IE6/6. Gracias por la visión –

2

JQuery solo puede garantizar que no haya fugas al realizar todas sus manipulaciones a través de la biblioteca. Hay rutinas en jQuery llamadas "empty" y "cleanData" que puedes leer para ver exactamente qué está pasando, pero básicamente el código simplemente separa todo lo que conoce de los elementos DOM antes de liberarlos. Se llama a esa rutina cuando haces algo como sobrescribir el contenido del elemento con ".html()" o ".load()".

Personalmente soy bastante cauteloso con términos como "garantía" en una situación como esta.

+0

Sé cuando elimina un elemento, lo limpia todo (por lo que mi ejemplo no es un ejemplo perfecto), pero por ejemplo en IE6, si queda una referencia circular cuando navega a la página siguiente, la memoria se fugará (cuando no eliminas el elemento). –

+0

Ah, veo lo que dices. Bueno, otra forma en que jQuery ayuda es que evita enganchar * cualquier cosa * directamente en los nodos DOM. – Pointy

+0

No le engancha cosas, pero técnicamente se engancha, lo que enlaza todos los eventos y datos de jQuery (que se limpian correctamente cuando se llama a .remove). –

1

reescrito para aclarar aún más

En realidad hay 2 causas de pérdidas de memoria en el ejemplo ofrecido. La primera pérdida de memoria se manifiesta debido a la creación de una referencia directa a un nodo DOM vivo dentro de un cierre. Los recolectores de basura (JS & DOM) de navegadores heredados como IE6 no pueden anular tales referencias. De ahí la necesidad de anular las referencias de nodo al final de su función.

jQuery evita esto de forma predeterminada debido a que los elementos DOM en vivo se adjuntan al objeto jQuery como atributos/propiedades, con lo que los recolectores de basura antes mencionados no tienen problemas para determinar las referencias nulas. Si el objeto jQuery tiene referencias nulas, simplemente se limpia y sus atributos/propiedades (en este caso, referencias a elementos DOM activos) junto con él.

Para evitar esta pérdida de memoria, es necesario hacer que un objeto mantenga la referencia al nodo DOM en vivo y luego hacer referencia al objeto en sus cierres. Los cierres solo mantendrán las referencias al objeto y no al elemento DOM vivo ya que esa referencia pertenece al objeto.

// will still leak, but not due to closure references, thats solved. 
function noLeak(){ 
    var obj= { 
     elem: document.createElement('div') 
    } 
    obj.elem.onclick = function(){ 
     obj.elem.innerHTML = obj.elem.innerHTML + "."; 
    } 
} 

Esto borró la referencia circular más obvia, pero todavía hay una fuga (onclick). El nodo tiene una referencia a una función que tiene una referencia al objeto que a su vez tiene una referencia al nodo. La única forma de eludir esta fuga es aprender del concurso addEvent (mucha gente trabajó para resolver esta fuga;)). De manera coincidente, el código necesario se puede encontrar allí, así que mis apologías para no proporcionar código para eso;)

La creación de un contenedor para el sistema de eventos agrega un poco más de código, pero es esencial. La idea principal es agregar un eventHandler común que delegue el evento a un caché/sistema de eventos que almacena las referencias requeridas. En un evento de descarga, la caché se limpia rompiendo las referencias circulares, lo que permite que los recolectores de basura (JS y DOM) arreglen sus propias esquinas.

+0

Todos los objetos jQuery tienen referencias a los elementos DOM que están manipulando, por lo que elem técnicamente -> DOMELEMENT -> onload function -> elem. Entonces, hay una referencia circular sucediendo. Y en mi JS real necesito usar el cierre para no anular el elem. –

+0

La referencia circular "A" no es la razón de la pérdida de memoria;) el motivo es "LA" referencia circular de un elemento DOM en vivo. Ver el apéndice arriba. – BGerrissen

+0

jQuery todavía tiene una referencia dentro de sí misma a un elemento DOM, todavía hay una referencia circular incluso si los eventos/datos no están atados al elemento DOM. - aunque tu última oración (en la respuesta) me intriga –

Cuestiones relacionadas