eventos elementos ejemplo dinámicamente creados asociar javascript dom javascript-events event-bubbling

elementos - getelementsbytagname javascript ejemplo



Si elimina un elemento DOM, ¿los eventos que comenzaron con ese elemento continúan burbujeando? (3)

¿Qué comportamiento debo esperar si elimino un elemento DOM que se usó para iniciar un evento burbuja, o cuyo hijo inició el evento burbuja - ¿continuará burbujeando si el elemento se elimina?

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 principal de la tabla (en lugar de adjuntar el evento a cada DT), y parece que no lo alcanza a veces.

EDITAR : Encontré este problema de nuevo, 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.


Ha pasado bastante tiempo desde que originalmente publiqué esta pregunta. Aunque la respuesta de TJCrowder 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 el elemento se cambia entre mousedown y mouseup, el ''clic'' no se dispara.

Según la Red de Desarrollo de Mozilla :

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

Entonces, cuando tiene un elemento DOM que está cambiando, puede enfrentar 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.

Las pruebas adicionales (vea el ejemplo en jsfiddle) muestran que si hace un clic, mantiene presionado el botón y espera que el elemento DOM cambie, y luego suelta el botón, podemos observar (en el contexto de jquery live):

  • El evento ''clic'' no se dispara
  • El evento ''mousedown'' se dispara para el primer nodo
  • El evento ''mouseup'' se activa para el nodo actualizado

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


Sí, debería continuar propagándose. Los eventos no tienen un vínculo real con el evento en el que dispararon, a excepción de la propiedad de 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.

Por otro 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 el elemento aún pueda ser referido a través de la propiedad event.target e incluso reinsertado antes de ser recolectado. No lo he intentado, así que solo es especulación.

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

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

Como TJ descubrió, ese no es el caso en IE. Pero la especificación de eventos DOM nivel 2 lo define como comportamiento correcto [énfasis mío] :.

Los eventos que se designan como burbujeo procederán inicialmente con el mismo flujo de eventos que los eventos que no burbujean. 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.


Empíricamente: depende de qué navegador estés usando; 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: la cabeza de Andy E encontró útilmente que DOM2 dice que el evento debería continuar porque el burbujeo debería basarse en el 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ás viendo es otra pregunta de hecho. 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. (Sin embargo, los hilos de trabajo vienen, ¡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 el evento UI en cola se procesa y no sucede en absoluto o no se repite porque el elemento ya no tiene un padre, que se eliminó. Si eso realmente puede suceder dependerá mucho de 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 lo hago a continuación.

Resultados empíricos para el aspecto does-bubbling-continue:

Probado Chrome 4 y Safari 4 (por ejemplo, WebKit), Opera 10.51, Firefox 3.6, IE6, IE7 e 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 e IE10 continúan el evento, por lo que IE no cumple con las especificaciones en IE8.

Página de prueba usando controladores 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 utilizando los attachEvent / addEventListener (a través de Prototype):

<!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>