Si se elimina un elemento DOM, ¿también se eliminan sus oyentes de la memoria?

Respuestas:

307

Navegadores modernos

JavaScript simple

Si un elemento DOM que se elimina no tiene referencias (no hay referencias que lo señalen), entonces , el elemento en sí es recogido por el recolector de basura, así como por cualquier controlador / escucha de eventos asociado con él.

var a = document.createElement('div');
var b = document.createElement('p');
// Add event listeners to b etc...
a.appendChild(b);
a.removeChild(b);
b = null; 
// A reference to 'b' no longer exists 
// Therefore the element and any event listeners attached to it are removed.

Sin embargo; Si hay referencias que todavía apuntan a dicho elemento, el elemento y sus oyentes de eventos se retienen en la memoria.

var a = document.createElement('div');
var b = document.createElement('p'); 
// Add event listeners to b etc...
a.appendChild(b);
a.removeChild(b); 
// A reference to 'b' still exists 
// Therefore the element and any associated event listeners are still retained.

jQuery

Sería justo suponer que los métodos relevantes en jQuery (como remove()) funcionarían exactamente de la misma manera (considerando que remove()se escribió usando, removeChild()por ejemplo).

Sin embargo, esto no es cierto ; la biblioteca jQuery en realidad tiene un método interno (que no está documentado y en teoría podría cambiarse en cualquier momento) llamado cleanData() (así es como se ve este método ) que limpia automáticamente todos los datos / eventos asociados con un elemento al eliminarlo del DOM (ya sea a través de este. remove(), empty(), html("")etc).


Navegadores antiguos

Se sabe que los navegadores más antiguos, específicamente las versiones anteriores de IE, tienen problemas de pérdida de memoria debido a que los oyentes de eventos mantienen referencias a los elementos a los que se adjuntaron.

Si desea una explicación más detallada de las causas, los patrones y las soluciones utilizadas para corregir las pérdidas de memoria de la versión IE heredada, le recomiendo que lea este artículo de MSDN sobre Comprender y resolver patrones de fugas de Internet Explorer.

Algunos artículos más relevantes para esto:

Eliminar manualmente los oyentes probablemente sea un buen hábito en este caso (solo si la memoria es tan vital para su aplicación y realmente está apuntando a tales navegadores).

dsgriffin
fuente
22
De acuerdo con la documentación de jquery cuando se utiliza el método remove () sobre un elemento, todos los oyentes de eventos se eliminan de la memoria. Esto afecta al elemento mismo y a todos los nodos secundarios. Si desea mantener los listados de eventos en memoria, debe usar .detach () en su lugar. Útil cuando los elementos eliminados se van a insertar nuevamente en el dom.
Lothre1
1
Si el elemento contiene elementos secundarios, ¿también separará a los enumeradores de eventos de los elementos secundarios?
CBeTJlu4ok
1
@ Lothre1: eso es solo cuando se utiliza el removemétodo. la mayoría de las veces el DOM simplemente se borrará por completo. (como enlaces turbo o algo así). Me pregunto cómo se ve afectada la memoria si lo hago document.body.innerHTML = ''...
vsync
1
Necesitaría más que "experiencia personal", más como datos duros y pruebas y enlaces a especificaciones que dicen cómo la memoria se mantiene persistente en los nodos que ya no están en el documento, esto es demasiado importante para confiar en la palabra de alguien sin pruebas :)
vsync
1
@ Lothre1 Gracias - He profundizado un poco más y descubrí cómo jQuery actúa de manera diferente al JavaScript normal a este respecto. Han actualizado la respuesta.
dsgriffin
23

con respecto a jQuery:

El método .remove () elimina elementos del DOM. Use .remove () cuando desee eliminar el elemento en sí, así como todo lo que contiene. Además de los elementos en sí, se eliminan todos los eventos vinculados y los datos jQuery asociados con los elementos. Para eliminar los elementos sin eliminar datos y eventos, use .detach () en su lugar.

Referencia: http://api.jquery.com/remove/

Código .remove()fuente de jQuery v1.8.2 :

remove: function( selector, keepData ) {
    var elem,
        i = 0;

    for ( ; (elem = this[i]) != null; i++ ) {
        if ( !selector || jQuery.filter( selector, [ elem ] ).length ) {
            if ( !keepData && elem.nodeType === 1 ) {
                jQuery.cleanData( elem.getElementsByTagName("*") );
                jQuery.cleanData( [ elem ] );
            }

            if ( elem.parentNode ) {
                elem.parentNode.removeChild( elem );
            }
        }
    }

    return this;
}

aparentemente jQuery usa node.removeChild()

De acuerdo con esto: https://developer.mozilla.org/en-US/docs/DOM/Node.removeChild ,

The removed child node still exists in memory, but is no longer part of the DOM. You may reuse the removed node later in your code, via the oldChild object reference.

es decir, los oyentes de eventos pueden ser eliminados, pero nodeaún existe en la memoria.

Sreenath S
fuente
3
Solo está agregando confusión: jQuery no hace nada que con los controladores tan simples removeChildno lo haría. Ambos también le devuelven una referencia que puede mantener para volver a unir este último (en cuyo caso, obviamente, permanece en la memoria) o tirar (en cuyo caso finalmente es recogido por GC y eliminado).
Oleg V. Volkov
yo sé: D. Entonces, ¿dónde está el que editó la pregunta? Porque podría haber jurado que había algo sobre el uso de jquery para eliminar un elemento DOM, en la pregunta anterior. ahora mi respuesta parece que estoy explicando cosas solo para acariciar mi ego. oye, siempre puedes
votar a favor
8

No dude en ver el montón para ver pérdidas de memoria en los controladores de eventos que mantienen una referencia al elemento con un cierre y el elemento que mantiene una referencia al controlador de eventos.

Al recolector de basura no le gustan las referencias circulares.

Caso habitual de pérdida de memoria: admitir que un objeto tiene una referencia a un elemento. Ese elemento tiene una referencia al controlador. Y el controlador tiene una referencia al objeto. El objeto tiene referencias a muchos otros objetos. Este objeto era parte de una colección que crees que has tirado al no hacer referencia a ella desde tu colección. => todo el objeto y todo lo que hace referencia permanecerá en la memoria hasta que salga la página. => debe pensar en un método de eliminación completo para su clase de objeto o confiar en un marco de mvc, por ejemplo.

Además, no dude en utilizar la parte del árbol de retención de las herramientas de desarrollo de Chrome.

lib3d
fuente
8

Solo extendiendo otras respuestas ...

Los controladores de eventos delegados no se eliminarán al eliminar elementos.

$('body').on('click', '#someEl', function (event){
  console.log(event);
});

$('#someEL').remove(); // removing the element from DOM

Revisa ahora:

$._data(document.body, 'events');
Lucky Soni
fuente
99
El controlador de eventos está conectado al cuerpo y no a #someEl, naturalmente, el controlador no debe eliminarse mientras el cuerpo aún esté aquí.
Yangshun Tay
Esto se puede eliminar manualmente: stackoverflow.com/questions/22400907/…
fangxing
7

En cuanto a jQuery, los siguientes métodos comunes también eliminarán otras construcciones, como los controladores de datos y eventos:

eliminar()

Además de los elementos en sí, se eliminan todos los eventos vinculados y los datos jQuery asociados con los elementos.

vacío()

Para evitar pérdidas de memoria, jQuery elimina otras construcciones, como los manejadores de datos y eventos, de los elementos secundarios antes de eliminar los elementos mismos.

html ()

Además, jQuery elimina otras construcciones como datos y controladores de eventos de elementos secundarios antes de reemplazar esos elementos con el nuevo contenido.

Jaskey
fuente
2

Sí, el recolector de basura también los eliminará. Sin embargo, no siempre puede ser el caso con los navegadores heredados.

Darin Dimitrov
fuente
77
su declaración debe estar respaldada por documentación API, ejemplos, etc.
Paolo Fulgoni