Pregunta ¿Los manejadores de eventos en un nodo DOM se eliminan con el nodo?


(Nota: estoy usando jQuery a continuación, pero la pregunta es realmente una general de Javascript).

Digamos que tengo un div#formsection cuyos contenidos se actualizan repetidamente usando AJAX, como este:

var formSection = $('div#formsection');
var newContents = $.get(/* URL for next section */);
formSection.html(newContents);

Cada vez que actualizo este div, yo desencadenar un evento personalizado, que vincula a los controladores de eventos con algunos de los elementos recién agregados, como este:

// When the first section of the form is loaded, this runs...
formSection.find('select#phonenumber').change(function(){/* stuff */});

...

// ... when the second section of the form is loaded, this runs...
formSection.find('input#foo').focus(function(){/* stuff */});

Entonces: estoy vinculando controladores de eventos a algunos nodos DOM, y luego, eliminando esos nodos DOM e insertando nuevos (html() hace eso) y vinculando manejadores de eventos a los nuevos nodos DOM.

¿Se eliminan mis controladores de eventos junto con los nodos DOM a los que están vinculados? En otras palabras, al cargar nuevas secciones, ¿hay muchos manejadores de eventos inútiles acumulándose en la memoria del navegador, esperando eventos en los nodos DOM que ya no existen, o se eliminan cuando se eliminan los nodos DOM?

Pregunta extra: ¿Cómo puedo probar esto yo mismo?


32
2017-12-02 16:55


origen


Respuestas:


Las funciones del manejador de eventos están sujetas a la misma recolección de basura que otras variables. Eso significa que se eliminarán de la memoria cuando el intérprete determine que no hay medios posibles para obtener una referencia a la función. Sin embargo, simplemente eliminar un nodo no garantiza la recolección de basura. Por ejemplo, tome este nodo y el controlador de eventos asociado

var node = document.getElementById('test');
node.onclick = function() { alert('hai') };

Ahora vamos a eliminar el nodo del DOM

node.parentNode.removeChild(node);

Asi que node ya no será visible en su sitio web, pero claramente todavía existe en la memoria, al igual que el controlador de eventos

node.onclick(); //alerts hai

Siempre que la referencia a node todavía es accesible de alguna manera, son propiedades asociadas (de las cuales onclick es uno) se mantendrá intacto.

Ahora intentémoslo sin crear una variable pendiente

document.getElementById('test').onclick = function() { alert('hai'); }

document.getElementById('test').parentNode.removeChild(document.getElementById('test'));

En este caso, parece que no hay otra manera de acceder al #test del nodo DOM, por lo que cuando se ejecuta un ciclo de recolección de basura, el onclick controlador debe ser eliminado de la memoria.

Pero este es un caso muy simple. El uso de cierres Javascript puede complicar en gran medida la determinación de la capacidad de recolección de basura. Intentemos enlazar una función de controlador de eventos un poco más compleja para onclick

document.getElementById('test').onclick = function() {
  var i = 0;
  setInterval(function() {
    console.log(i++);
  }, 1000);

  this.parentNode.removeChild(this);
};

Por lo tanto, cuando haga clic en #test, el elemento se eliminará instantáneamente, sin embargo, un segundo después, y cada segundo después, verá un número incrementado impreso en su consola. El nodo se elimina y no es posible hacer más referencia a él, pero parece que aún quedan partes de él. En este caso, es probable que la función del controlador de eventos no se conserve en la memoria, pero sí el alcance que creó.

Entonces la respuesta que creo es; depende. Si hay referencias colgantes y accesibles a los nodos DOM borrados, sus manejadores de eventos asociados todavía residirán en la memoria, junto con el resto de sus propiedades. Incluso si este no es el caso, el alcance creado por las funciones del controlador de eventos aún podría estar en uso y en la memoria.

En la mayoría de los casos (y felizmente ignorando IE6) es mejor simplemente confiar en que Garbage Collector hará su trabajo, después de todo, Javascript no es C. Sin embargo, en casos como el último ejemplo, es importante escribir funciones de destructor de algún tipo para apagar la funcionalidad implícitamente.


21
2017-12-02 17:45



jQuery hace todo lo posible para evitar pérdidas de memoria al eliminar elementos del DOM. Siempre que use jQuery para eliminar nodos DOM, jQuery debe manejar la eliminación de los controladores de eventos y los datos adicionales. Recomiendo leer John Resig's Secretos de un JavaScript Ninja a medida que detalla las posibles filtraciones en diferentes navegadores y cómo las bibliotecas de JavaScript como jQuery solucionan estos problemas. Si no está utilizando jQuery, definitivamente debe preocuparse por filtrar la memoria a través de manejadores de eventos huérfanos al eliminar los nodos DOM.


6
2017-12-02 17:56



Es posible que deba eliminar esos controladores de eventos.

La memoria Javascript se filtra después de descargar una página web

En nuestro código, que no está basado en jQuery, sino en algunos prototipos desviados, tenemos inicializadores y destructores en nuestras clases. Descubrimos que es absolutamente esencial eliminar los controladores de eventos de los objetos DOM cuando destruimos no solo nuestra aplicación sino también widgets individuales durante el tiempo de ejecución.

De lo contrario, terminamos con pérdidas de memoria en IE.

Es sorprendentemente fácil obtener pérdidas de memoria en IE, incluso cuando descargamos la página, debemos asegurarnos de que la aplicación "se apaga" limpiamente, ordenando todo, o el proceso de IE crecerá con el tiempo.

Editar: Para hacer esto correctamente, tenemos un observador de evento en window Para el unload evento. Cuando llega ese evento, nuestra cadena de destructores es llamada para limpiar adecuadamente cada objeto.

Y algunos ejemplos de código:

/**
 * @constructs
 */
initialize: function () {
    // call superclass
    MyCompany.Control.prototype.initialize.apply(this, arguments);

    this.register(MyCompany.Events.ID_CHANGED, this.onIdChanged);
    this.register(MyCompany.Events.FLASHMAPSV_UPDATE, this.onFlashmapSvUpdate);
},

destroy: function () {

    if (this.overMap) {
        this.overMap.destroy();
    }

    this.unregister(MyCompany.Events.ID_CHANGED, this.onIdChanged);
    this.unregister(MyCompany.Events.FLASHMAPSV_UPDATE, this.onFlashmapSvUpdate);

    // call superclass
    MyCompany.Control.prototype.destroy.apply(this, arguments);
},

2
2017-12-02 17:14



No necesariamente

los documentación sobre jQuery's empty() método ambos responden mi pregunta y me dan una solución a mi problema. Dice:

Para evitar pérdidas de memoria, jQuery elimina   otras construcciones tales como datos y   controladores de eventos de los elementos secundarios   antes de quitar los elementos   sí mismos.

Entonces: 1) si no hiciéramos esto explícitamente, obtendríamos pérdidas de memoria, y 2) al usar empty(), Puedo evitar esto.

Por lo tanto, debería hacer esto:

formSection.empty();
formSection.html(newContents);

Todavía no está claro para mí si .html() se ocuparía de esto por sí mismo, pero una línea adicional para estar seguro no me molesta.


2
2017-12-02 17:49



Quería conocerme a mí mismo, así que después de una pequeña prueba, creo que la respuesta es sí.

Se llama a removeEvent cuando eliminas () algo del DOM.

Si quieres verlo tú mismo, puedes intentarlo y seguir el código estableciendo un punto de interrupción. (Estaba usando jquery 1.8.1)

Agregue un nuevo div primero:
$('body').append('<div id="test"></div>') 

  Comprobar $.cache para asegurarse de que no haya eventos asociados. (debe ser el último objeto)

  Adjunte un evento de clic a él:
$('#test').on('click',function(e) {console.log("clicked")});

  Pruébelo y vea un nuevo objeto en $.cache:
$('#test').click()

  Quítalo y puedes ver el objeto en $.cache se ha ido también:
$('#test').remove()


1
2017-10-20 04:45