2010-10-12 7 views
5

Tengo una situación difícil: quiero enviar algunos datos con un detector de eventos pero también puedo eliminar el oyente. Aquí está el enfoque de cierre estándar ...Enviar datos con un detector de eventos

var fn = function(e){method(e,data)}; 
el.addEventListener('click',fn,false); 
el.removeEventListener('click',fn,false); 

y puede eliminar el evento, muy bien. Pero, por ejemplo, ¿el elemento se eliminó del DOM? Entonces, se quedaría con la función fn sentada. Después de eliminar un par de miles de elementos DOM, dará como resultado una pérdida de memoria.

He considerado adjuntar un controlador de eventos DOMNodeRemoved, que eliminaría cualquier resto de funciones/datos junto con el nodo eliminado. Pero aparentemente, ese evento no es compatible con varios navegadores.

La única otra opción que he encontrado sería la modificación del DOM del elemento. Considere ...

el.MyEventData = function(e){method(e,data)}; 
el.addEventListener('click',el.MyEventData,false); 
el.removeEventListener('click',el.MyEventData,false); 

Está modificando el DOM aceptable en esta situación? La única parte pegajosa de esa solución es cuando intenta agregar más de un detector de eventos. Digamos que hicimos una función personalizada para analizar la adición/eliminación de los acontecimientos ...

function makeEvent(fn,data){ 
    var dataFn = function(e){fn(e,data)}; 
    //create object to hold all added events 
    el.myEvents = {}; 
    //make ID for this specific event 
    var eventID = ranString(); 
    //add the event to the events object 
    el.myEvents[eventID] = [fn,dataFn]; 
    //finally add the listener 
    el.addEventListener('click',dataFn,false); 
} 
function destroyEvent(fn){ 
    //find all fn references 
    for(var id in el.myEvents){ 
     if (el.myEvents[id][0] == fn){ 
      el.removeEventListener('click',el.myEvents[id][1],false); 
      el.myEvents[id] = null; 
     } 
    } 
} 

Todavía modifica el DOM, como antes, y desde luego no es una solución muy elegante tampoco. ¿Alguien sabe de alguna alternativa, mejor método para pasar datos?

EDIT: he examinado un poco de los guiones de datos/eventos de jQuery. No entiendo completamente el código, así que si alguien aclara, sería útil. Pero parece que usan un método similar, al crear algún tipo de propiedad el.cache, que contiene datos de eventos.

+0

el evento jQuery se basa en la idea de [decano edward] (http://dean.edwards.name/weblog/2005/10/add-event/), es probablemente la mejor solución para usted –

+0

He buscado su script, pero no tiene otra forma de implementar datos envío que los ejemplos que ya he enumerado. – Azmisov

Respuesta

2

Teniendo en cuenta que usted usa addEventListener esto es no es un problema ya que todos los recolectores de basura modernos pueden encargarse de tales situaciones. El problema con los detectores de eventos solo existe en la implementación de IE (7-).

Test - 10 000 addEventListener y quitar elemento (véase el Administrador de tareas de Windows)

Cuando un objeto DOM contiene una referencia a un objeto JavaScript (tal evento función de tratamiento), y cuando ese objeto JavaScript contiene una referencia a ese objeto DOM, se forma una estructura cíclica . Esto es no en sí mismo un problema. En el momento en que no hay otras referencias al objeto DOM y el controlador de eventos, a continuación, la basura colector (un administrador de recursos memoria automática) reclamará los dos, lo que permite su espacio para ser reasignaron . El recopilador de basura de JavaScript entiende sobre ciclos y no se confunde con ellos.

http://www.crockford.com/javascript/memory/leak.html

+0

no se trata de eliminar un oyente de un elemento que desea mantener. – lincolnk

+0

porque si agrega un oyente y luego lo elimina, no pasa nada malo. Incluso en IE. Los problemas (fuga) solo ocurren cuando crea referencias cíclicas y luego elimina los elementos sin borrar los manejadores de eventos de antemano. – galambalazs

0

Una posible solución tal vez lo lleve en una dirección diferente: agregue la función como un hermano en línea del elemento.

<span id='element12345'>Test</span><script 
    type='text/javascript'>function fn12345() { /* ... */ }</script> 

Luego, cuando se quita todos los detectores de eventos que desea, también puede eliminar el "nextSibling()" del elemento que está trabajando.

0

¿Qué tal una configuración como esta? (Utilizando la sintaxis de IE ya que es lo que tengo disponible en este momento)

<div id="clickbox" style="width: 100px; height: 100px; border: 1px solid orange;"> 
    click here to test</div> 
<input id="Button1" type="button" value="clear handler" /> 
<script> 

    var data = "derp1"; 
    var el = document.getElementById('clickbox'); 
    var btn = document.getElementById('Button1'); 

    // methods 
    var method = function (e, dat2) { alert(dat2); }; 
    var fn = function (e) { method(e, data) }; 
    var remover = null; 

    // attachment 
    el.attachEvent('onclick', fn, false); 

    (function (id, handler) { 

     // handler variable is local but points to the right function 
     remover = function (e) { 

      if (document.getElementById(id)) { 
       // remove the original listener (handler is fn) 
       document.getElementById(id).detachEvent('onclick', handler, false); 
       alert('detached'); 
      } 

      // remove last reference to the original method 
      handler = null; 
      alert('method nulled'); 

      // clean up the remover method 
      e.srcElement.detachEvent('onclick', remover); 
      remover = null; 
     }; 

     btn.attachEvent('onclick', remover); 

    })('clickbox', fn); 

    // clear the original variable but the method still exists as an event listener 
    fn = null; 

    // you should be able to remove the div element and any references to it 
    // without leaving any stray bits around. 

    setTimeout(function() { 
        var d = document.getElementById('clickbox'); 
        if (d){ d.parentNode.removeChild(d) ; } 
    } , 6000); 

    el = null; 
    btn = null; 

</script> 

Asumo que no quiere que el oyente removido inmediatamente después de añadirlo sino que quiere ser capaz de eliminarlo en un momento posterior. para hacer frente a esto, la rutina de limpieza tiene su propio alcance al crear una función anónima que se invoca inmediatamente con fn como parámetro. la función anon tiene entonces su propia referencia a fn mantenida en la variable handler. después de eso, fn se puede limpiar y las referencias restantes al método original existen en la lista de oyentes para su elemento y en el alcance de la función anónima.

dentro del alcance de la función anónima, la función remover tiene acceso a la variable handler y puede usarla para separar el oyente. remover se separa y se borra por sí solo, por lo que no debería quedar nada con acceso a ninguna versión de fn/handler.

No tengo ninguna forma de verificar todo esto en este momento, pero creo que tiene sentido y debe mantenerse en navegadores modernos.

+0

Entonces, si obtengo lo que intentas hacer, la secuencia de comandos permite eliminar el evento adjunto registrando un evento de eliminación con otro elemento. De esta forma, cuando haga clic en el botón Eliminar, ya tendrá una referencia a la función y la eliminará. Pero, ¿hay alguna forma en que el script permita eliminar el evento simplemente llamando a algún código JavaScript sin agregar otro evento a otro elemento? – Azmisov

+0

el clic del botón es solo una forma de probar el script llamando a 'removedor'. Si elimina las llamadas 'btn.attachEvent/detachEvent' de este script,' removedor' se mantendrá como una función de limpieza predefinida hasta que esté listo para usarlo. también podría reemplazar el parámetro 'e' con los parámetros que necesite, o nada. solo está ahí para limpiar las cosas extra de demostración. – lincolnk

+0

Aún tendría que tener un 'eliminador' separado para cada evento que registre, ya que cada uno debe tener una referencia única a la función de manejo de cada evento (ya que los manejadores pueden estar llamando a métodos diferentes). Revisé tu demostración y, una vez que se eliminó, la función removedor aún existe. Necesito alguna forma de destruir el eliminador (o caché), para que no haya pérdidas de memoria. – Azmisov

1

¿Consideró .delegate()?

+0

Muy interesante. No es una función estándar, creo, pero lo investigaré. – Azmisov

+0

Creo que .apply() o .call() son las funciones reales necesarias. Sin embargo, todavía no resuelven el problema. – Azmisov

1

De acuerdo con su pregunta jQuery:

Cada objeto tiene una propiedad jQ data. Es no almacenado dentro del elemento en sí, es muy importante. jQ usa almacenamiento general para todos los elementos: jQuery.cache. Así que cuando se agrega nada al elemento de la siguiente manera:

$('#myEl').data('someValue', 1);

jQ haga lo siguiente:

jQuery.cache[elementUniqId]['someValue'] = 1;

Así elemento no contiene el objeto de datos. Solo tiene una identificación única que le permite acceder al registro de datos en el almacenamiento global. (elementUniqId se autogenerado)

eventos

JQ se almacenan en los datos de los elementos así:

$('#myEl').click(function() { first listener }); 
$('#myEl').mouseenter(function() { one more listener }); 
$('#myEl').click(function() { anotheer listener }); 

se almacenarán:

jQuery.cache[elementUniqId]['events'] = { 
    click: [function() { first listener }, function() { anotheer listene }], 
    mouseenter: [function() { one more listener }] 
}; 

Permite jQ para almacenar la orden de ejecución para todos los oyentes adjunto a cada evento. Y luego, cuando elimina el elemento dom, usando jQuery - .remove(), jQuery recorre el jQuery.cache[elementUniqId]['events'] y elimina cada elemento de escucha del elemento, y luego elimina el registro del elemento caché.Permite jQ a prevén pérdidas de memoria

+0

Entonces, jQuery solo puede eliminar el caché si se usó .remove()? Supongamos que si una solicitud de Ajax cambiaba la página, jQuery no eliminaría la memoria caché de la página anterior. – Azmisov

+0

Correcto, así que supongo que en caso de que esté recargando contenido muchas veces agregando eventos, etc., primero debe 'eliminar' antes de insertar uno nuevo. Por otro lado, como está agregando evento no directamente al elemento, sino a su registro en el almacenamiento general, no debería haber muchas filtraciones. O si es posible, puede usar la delegación de eventos en el contenido cargado. – fantactuka

Cuestiones relacionadas