2010-04-28 15 views
17

¿Qué comportamiento debo esperar si elimino un elemento DOM que se usó para iniciar una burbuja de evento o cuyo hijo inició la burbuja de evento? ¿Seguirá burbujeando si se elimina el elemento?Si elimina un elemento DOM, ¿los eventos que comenzaron con ese elemento continúan burbujeando?

Por ejemplo, supongamos que tiene una tabla y desea detectar eventos de clic en las celdas de la tabla. Otra pieza de JS ha ejecutado una solicitud de AJAX que eventualmente reemplazará la tabla, en su totalidad, una vez que se complete la solicitud.

¿Qué sucede si hago clic en la tabla, e inmediatamente después de que la tabla se reemplaza por una finalización exitosa de una solicitud de AJAX? Lo pregunto porque veo un comportamiento donde los eventos de clics no parecen burbujear, pero es difícil de duplicar.

Estoy viendo el evento en un elemento padre de la tabla (en lugar de adjuntar el evento a cada TD), y parece que no lo alcanza a veces.

EDIT: Encontré de nuevo este problema, y ​​finalmente llegué a la raíz. ¡No fue un problema de burbujeo de eventos en absoluto! Ver mi respuesta a continuación para más detalles.

+0

Interesante pregunta. FWIW, debería ser fácil de probar: anide dos elementos, mire (por separado) los clics en ambos, en el controlador de clics en el niño, elimine al niño ('this.parentNode.removeChild (this)'), y vea si el evento burbujea hasta el padre. (Saliendo por la puerta o lo habría intentado yo mismo.) –

+0

Decidí quedarme e intentarlo, vea abajo. –

Respuesta

23

Empíricamente: Depende del navegador que está utilizando; IE cancela el evento, todo lo demás (por lo que yo sé) lo continúa. Vea las páginas de prueba y la discusión a continuación.

Teóricamente:Andy E's head amablemente found que DOM2 says the event should continue porque burbujeo debe basarse en la estado inicial del árbol. Entonces, el comportamiento de la mayoría es correcto, IE está solo aquí. Quelle sorpresa.

Pero: Si eso se relaciona con lo que está viendo, es otra pregunta. Está buscando clics en un elemento principal de la tabla, y lo que sospecha es que muy raramente, cuando hace clic en la tabla, hay una condición de carrera con una finalización Ajax que reemplaza la tabla y se pierde el clic. Esa condición de carrera no puede existir dentro del intérprete de Javascript porque, por ahora, Javascript en los navegadores es de un solo subproceso. (Los hilos del trabajador están llegando, aunque   — ¡whoo hoo!) Pero en teoría, el clic podría suceder y ser puesto en cola por un hilo UI no Javascript en el navegador, entonces el ajax podría completar y reemplazar el elemento y luego la interfaz de usuario en cola el evento se procesa y no ocurre en absoluto o no se repite porque el elemento ya no tiene un elemento principal, que se eliminó. Si eso realmente puede suceder dependerá de un lote en la implementación del navegador. Si lo está viendo en cualquier navegador de código abierto, puede consultar su fuente para poner en cola eventos de IU para que el intérprete los procese. Pero esa es una cuestión diferente a la de eliminar el elemento con el código dentro del controlador de eventos como el siguiente.

resultados empíricos para el aspecto no-burbujeo-continuar:

Probado Chrome 4 y Safari 4 (por ejemplo, WebKit), Opera 10.51, Firefox 3.6, IE6, IE7, y IE8. IE fue el único que canceló el evento cuando eliminó el elemento (y lo hizo de manera consistente en todas las versiones), ninguno de los otros lo hizo. No parece importar si está usando manejadores DOM0 o más modernos.

ACTUALIZACIÓN: En las pruebas, IE9 y IE10 siguen el evento, por lo que el incumplimiento de las especificaciones IE se detiene en IE8.

página de prueba con los manipuladores Dom0:

<!DOCTYPE HTML> 
<html> 
<head> 
<meta http-equiv="Content-type" content="text/html;charset=UTF-8"> 
<title>Test Page</title> 
<style type='text/css'> 
body { 
    font-family: sans-serif; 
} 
#log p { 
    margin:  0; 
    padding: 0; 
} 
</style> 
<script type='text/javascript'> 
window.onload = pageInit; 

function pageInit() { 
    var parent, child; 

    parent = document.getElementById('parent'); 
    parent.onclick = parentClickDOM0; 
    child = document.getElementById('child'); 
    child.onclick = childClickDOM0; 
} 

function parentClickDOM0(event) { 
    var element; 
    event = event || window.event; 
    element = event.srcElement || event.target; 
    log("Parent click DOM0, target id = " + element.id); 
} 

function childClickDOM0(event) { 
    log("Child click DOM0, removing"); 
    this.parentNode.removeChild(this); 
} 

function go() { 
} 

var write = log; 
function log(msg) { 
    var log = document.getElementById('log'); 
    var p = document.createElement('p'); 
    p.innerHTML = msg; 
    log.appendChild(p); 
} 

</script> 
</head> 
<body><div> 
<div id='parent'><div id='child'>click here</div></div> 
<hr> 
<div id='log'></div> 
</div></body> 
</html> 

página de prueba con attachEvent/addEventListener manipuladores (a través de Prototipo):

<!DOCTYPE HTML> 
<html> 
<head> 
<meta http-equiv="Content-type" content="text/html;charset=UTF-8"> 
<title>Test Page</title> 
<style type='text/css'> 
body { 
    font-family: sans-serif; 
} 
#log p { 
    margin:  0; 
    padding: 0; 
} 
</style> 
<script type='text/javascript' src='http://ajax.googleapis.com/ajax/libs/prototype/1.6.1.0/prototype.js'></script> 
<script type='text/javascript'> 
document.observe('dom:loaded', pageInit); 
function pageInit() { 
    var parent, child; 

    parent = $('parent'); 
    parent.observe('click', parentClick); 
    child = $('child'); 
    child.observe('click', childClick); 
} 

function parentClick(event) { 
    log("Parent click, target id = " + event.findElement().id); 
} 

function childClick(event) { 
    log("Child click, removing"); 
    this.remove(); 
} 

function go() { 
} 

var write = log; 
function log(msg) { 
    $('log').appendChild(new Element('p').update(msg)); 
} 
</script> 
</head> 
<body><div> 
<div id='parent'><div id='child'>click here</div></div> 
<hr> 
<div id='log'></div> 
</div></body> 
</html> 
+1

+1 y el cheque verde por ser tan completo! – Matt

+1

@ T.J.Brillante detalle y resumen, la respuesta perfecta, lástima que no puedo dar otro +1 jajaja. –

+0

@Andy: LOL, gracias, ¡y gracias por encontrar la información de DOM2! –

5

Sí, debería seguir propagándose. Los eventos no tienen ningún vínculo real con el evento en el que dispararon, a excepción de la propiedad target. Cuando elimina el elemento, el código interno que propaga el evento no debe tener ningún "conocimiento" de que el elemento original ha pasado del documento visible.

Como un lado, el uso de removeChild no eliminará un elemento de inmediato, simplemente lo separa del árbol de documentos. Un elemento solo se debe eliminar/recoger basura cuando no haya referencias a él. Por lo tanto, es posible que aún se pueda hacer referencia al elemento a través de la propiedad event.target e incluso volver a insertarlo antes de que se recolecte la basura. No lo he probado, así que solo es especulación.


T.J. El comentario de Crowder me hizo decidir hacer un rápido ejemplo. Estuve en lo cierto en ambos aspectos, sí funciona y aún puedes obtener una referencia al nodo eliminado usando event.target.

http://jsbin.com/ofese/2/


Como T. J. descubierto, ese no es el caso en IE. Pero la especificación DOM Level 2 Events does define it as correct behavior [énfasis mío] :.

Los eventos que se designan como burbujeo comenzarán inicialmente con el mismo flujo de eventos que los eventos sin burbujas. El evento se envía a su EventTarget de destino y cualquier detector de eventos encontrado allí se activa. Los eventos de burbujeo activarán los detectores de eventos adicionales que se encuentren al seguir hacia arriba la cadena principal de EventTarget, verificando si hay detectores de eventos registrados en cada EventTarget sucesivo. Esta propagación hacia arriba continuará hasta e incluyendo el Documento. Los EventListeners registrados como capturadores no se activarán durante esta fase. La cadena de EventTargets desde el objetivo del evento hasta la parte superior del árbol se determina antes del envío inicial del evento. Si se producen modificaciones en el árbol durante el proceso del evento, el flujo del evento continuará en función del estado inicial del árbol.

+0

@Andy: en realidad, no, depende del navegador. Decidí quedarme e intentarlo, ver mi respuesta. –

+0

@ T.J. ja, ja, sabía que debería haber probado IE. Vale la pena ser minucioso, supongo, +1 para ti! Aún así, no puedo entender por qué IE haría esto (aunque realmente no me sorprende tanto), va contra toda lógica si me preguntas. Como dije en mi respuesta, con 'removeChild', el elemento no se elimina por completo, solo se separa del árbol DOM y se puede volver a colocar. Simplemente no tiene sentido cancelar el evento. –

+0

@Andy: Sí, * siempre * prueba con IE, muchas cosas son diferentes. :-) Puedo ver su interpretación, utilizando esta (lógica completamente hipotética): 1. Dispatcher recibe el evento 2. Dispatcher distribuye el evento en el elemento 3. Handler elimina el elemento 4. Dispatcher comprueba el indicador 'cancelBubble' y es falso 5. Dispatcher mira 'element.parentNode' - pero es' nulo'; deja de enviar Mientras que los otros navegadores parecen captar toda la ascendencia * antes *, comienzan a enviar el evento. A menos que la especificación sea clara sobre lo que debería suceder, no veo ninguno de los comportamientos como incorrectos. :-) –

5

Ha sido bastante tiempo desde que Iniciado esta pregunta. Aunque la respuesta de T.J.Crowder fue muy informativa (como la de Andy E), y me dijo que debería funcionar, seguí viendo un problema. Lo dejé de lado por un tiempo, pero volví a visitarlo hoy cuando volví a encontrar el mismo problema en otra aplicación web.

Jugué con eso por un tiempo, y me di cuenta de cómo duplicar el problema cada vez (al menos en FF3.6 y Chrome 8). El problema no era que la burbuja del evento se cancelara o se perdiera cuando se eliminó el elemento DOM. En cambio, el problema es que si se cambia el elemento entre mousedown y mouseup, el 'clic' no se activará.

por la Mozilla Development Network:

El evento click se eleva cuando el usuario hace clic en un elemento. El evento click ocurrirá después de los eventos mousedown y mouseup.

Por lo tanto, cuando tiene un elemento DOM que está cambiando, puede encontrar este problema. Y creí erróneamente que la burbuja del evento se estaba perdiendo. Simplemente sucede que si tiene un elemento que se actualiza con frecuencia, lo verá con más frecuencia (que es mi caso) y es menos probable que lo pase como un golpe de suerte.

pruebas adicionales (ver el example en jsFiddle) muestra que si se hace clic, sostiene el botón y espera a que el elemento DOM para cambiar, y luego suelta el botón, podemos observar (en el contexto vivo jQuery):

  • el evento 'clic' hace no fuego
  • Se desencadena el evento MouseDown '' para el primer nodo
  • los mouseup '' activa el evento para el nodo actualizado

EDITAR: Probado en IE8. IE8 dispara el mousedown para el primer nodo, mouseup para el nodo actualizado, pero hace, de hecho, activa 'click', usando el nodo actualizado como el origen del evento.

+1

Cosas interesantes. También parece que Internet Explorer lo tiene aquí mismo; compruebe el [borrador de especificación de eventos DOM3] (http://dev.w3.org/2006/webapi/DOM-Level-3-Events/html/DOM3-Events.html#event-type-click) - * "The el evento click puede estar precedido por los eventos mousedown y mouseup en el mismo elemento, sin tener en cuenta los cambios entre otros tipos de nodos (por ejemplo, nodos de texto). * - básicamente diciendo que siempre que el elemento superior debajo del mouse permanezca igual evento debe disparar. Si el innerHTML cambia, sin embargo, no debería. –

+0

Volvió a encontrar esta pregunta dos años más tarde y este problema está resuelto en el último Firefox (aunque su Fiddle necesita un poco de trabajo) pero no Webkit, por desgracia. ¡Puede valer la pena publicar un informe de error! –

Cuestiones relacionadas