¿Cómo encontrar oyentes de eventos en un nodo DOM al depurar o desde el código JavaScript?

842

Tengo una página donde algunos oyentes de eventos están conectados a cuadros de entrada y cuadros de selección. ¿Hay alguna manera de averiguar qué oyentes de eventos están observando un nodo DOM particular y para qué evento?

Los eventos se adjuntan utilizando:

  1. Prototipos Event.observe ;
  2. DOM's addEventListener;
  3. Como atributo del elemento element.onclick.
Navneet
fuente
3
¿Cómo se adjuntan los eventos en primer lugar? ¿Está utilizando una biblioteca (por ejemplo, Prototype, jQuery, etc.)?
Crescent Fresh
Es importante tener en cuenta que se pueden adjuntar múltiples funciones de devolución de llamada para el mismo tipo de evento a través de element.addEventListener(type, callback, [bubble]), mientras que element.onclick = functionse sobrescribirán cada vez que asigne.
Braden Best

Respuestas:

507

Si solo necesita inspeccionar lo que sucede en una página, puede probar el bookmarklet Visual Event .

Actualización : Visual Event 2 disponible.

Andrew Hedges
fuente
1
¡Perfecto! Completa el EventBugcomplemento de Firebug .
Robin Maben
22
Esto agrega un billón de elementos a la página, muchos de los cuales son imágenes. Su utilidad se reduce en gran medida en una página con muchos controladores de eventos (el mío tiene 17k y Visual Event tardó unos 3 minutos en cargarse).
Tony R
15
Creo que la extensión de Chrome @ssharma mencionada es la que está disponible aquí
kmote
1
Visial Event no funciona si la página hace referencia a una biblioteca js de terceros. Provoca un error: XMLHttpRequest no puede cargar A.com/js/jquery-ui-1.10.3.custom.js?_=1384831682813 . Origin B.com no está permitido por Access-Control-Allow-Origin.
autopista
55
¡Las herramientas de desarrollador internas de Chrome y FF (F12) tienen un visor de eventos incorporado! Chrome: en la pestaña "Elementos", seleccione y elemento y vea a la derecha: Estilo, Computado, Oyentes de eventos
oriadam
365

Depende de cómo se adjuntan los eventos. Por ejemplo, supongamos que tenemos el siguiente controlador de clics:

var handler = function() { alert('clicked!') };

Vamos a adjuntarlo a nuestro elemento utilizando diferentes métodos, algunos que permiten la inspección y otros que no.

Método A) controlador de evento único

element.onclick = handler;
// inspect
alert(element.onclick); // alerts "function() { alert('clicked!') }"

Método B) controladores de eventos múltiples

if(element.addEventListener) { // DOM standard
    element.addEventListener('click', handler, false)
} else if(element.attachEvent) { // IE
    element.attachEvent('onclick', handler)
}
// cannot inspect element to find handlers

Método C): jQuery

$(element).click(handler);
  • 1.3.x

    // inspect
    var clickEvents = $(element).data("events").click;
    jQuery.each(clickEvents, function(key, value) {
        alert(value) // alerts "function() { alert('clicked!') }"
    })
  • 1.4.x (almacena el controlador dentro de un objeto)

    // inspect
    var clickEvents = $(element).data("events").click;
    jQuery.each(clickEvents, function(key, handlerObj) {
        alert(handlerObj.handler) // alerts "function() { alert('clicked!') }"
        // also available: handlerObj.type, handlerObj.namespace
    })

(Ver jQuery.fn.datay jQuery.data)

Método D): prototipo (desordenado)

$(element).observe('click', handler);
  • 1.5.x

    // inspect
    Event.observers.each(function(item) {
        if(item[0] == element) {
            alert(item[2]) // alerts "function() { alert('clicked!') }"
        }
    })
  • 1.6 a 1.6.0.3, inclusive (se hizo muy difícil aquí)

    // inspect. "_eventId" is for < 1.6.0.3 while 
    // "_prototypeEventID" was introduced in 1.6.0.3
    var clickEvents = Event.cache[element._eventId || (element._prototypeEventID || [])[0]].click;
    clickEvents.each(function(wrapper){
        alert(wrapper.handler) // alerts "function() { alert('clicked!') }"
    })
  • 1.6.1 (un poco mejor)

    // inspect
    var clickEvents = element.getStorage().get('prototype_event_registry').get('click');
    clickEvents.each(function(wrapper){
        alert(wrapper.handler) // alerts "function() { alert('clicked!') }"
    })
Creciente Fresco
fuente
3
Gracias por actualizar esto. Es lamentable que tenga que iterar a través de cada tipo de controlador.
Keith Bentrup
44
En el "Método B" (addEventListener) aquí hay una respuesta con respecto al estado de las instalaciones de enumeración para manejadores registrados con API de eventos DOM pura: stackoverflow.com/questions/7810534/…
Nickolay
@ John: gracias por el comentario. ¿Tiene un ejemplo de "JavaScript real" para buscar oyentes previamente agregados a través de addEventListener?
Crescent Fresh
66
@ Jan, esto no parece funcionar para jQuery 1.7.2, ¿hay un enfoque diferente para esa versión?
tomdemuyt
14
@tomdemuyt En jQuery, los eventos ahora se almacenan en una matriz de datos interna en lugar de ser accesibles .data('events')como antes. Para acceder a los datos de eventos internos, use $._data(elem, 'events'). Tenga en cuenta que esta función está marcada "solo para uso interno" en la fuente jQuery, por lo que no promete que siempre funcionará en el futuro, pero creo que ha funcionado desde jQuery 1.7 y aún funciona.
Matt Browne
351

Chrome, Firefox, Vivaldi y Safari son compatibles getEventListeners(domElement)con su consola de herramientas de desarrollador.

Para la mayoría de los propósitos de depuración, esto podría usarse.

A continuación hay una muy buena referencia para usarlo: https://developers.google.com/web/tools/chrome-devtools/console/utilities#geteventlisteners

Raghav
fuente
1
+1. El código específico del navegador no es un problema para mí, porque estoy tratando de que una página que no controlo funcione correctamente.
Grault
99
agradable, pero solo funciona en la línea de comandos de la consola Chrome :(
gillyspy
55
@Raghav ah gracias, el uso NO es : como pensé por primera vez :)getEventListeners(object) obj getEventListeners()
jave.web
11
Podrías haberme ahorrado 3 horas si me hubieras explicado cómo obtener el código fuente de un oyente de eventos. Esa "referencia" no explicaba nada. En caso de que alguien más estaba en una pérdida aquí es cómo: var list = getEventListeners(document.getElementById("YOURIDHERE")); console.log(list["focus"][0]["listener"].toString()). Cambie "foco" al evento que desea inspeccionar y al número si hay varios oyentes en el mismo evento.
doABarrelRoll721
46
CONSEJO: getEventListeners($0)obtendrá los oyentes de eventos para el elemento en el que se ha centrado en las herramientas de desarrollo de Chrome. Uso esto todo el tiempo. Me encanta no tener que usar querySelector o get__By_ funciones
cacoder
91

WebKit Inspector en los navegadores Chrome o Safari ahora hace esto. Mostrará los detectores de eventos para un elemento DOM cuando lo seleccione en el panel Elementos.

Ishan
fuente
99
No estoy seguro de que muestre todos los controladores de eventos; solo el único controlador de eventos HTML.
huyz
3
Debo mencionar el complemento EventBug para Firebug para completar < softwareishard.com/blog/category/eventbug >
Nickolay
Esto me alegró el día, un código prototipo heredado tenía múltiples funciones vinculadas al mismo evento en el mismo elemento y estaba muriendo tratando de rastrearlas. Muchas gracias Ishan.
siliconrockstar
70

Es posible enumerar todos los oyentes de eventos en JavaScript: no es tan difícil; solo tiene que hackear el prototypemétodo de los elementos HTML ( antes de agregar los oyentes).

function reportIn(e){
    var a = this.lastListenerInfo[this.lastListenerInfo.length-1];
    console.log(a)
}


HTMLAnchorElement.prototype.realAddEventListener = HTMLAnchorElement.prototype.addEventListener;

HTMLAnchorElement.prototype.addEventListener = function(a,b,c){
    this.realAddEventListener(a,reportIn,c); 
    this.realAddEventListener(a,b,c); 
    if(!this.lastListenerInfo){  this.lastListenerInfo = new Array()};
    this.lastListenerInfo.push({a : a, b : b , c : c});
};

Ahora cada elemento de anclaje ( a) tendrá una lastListenerInfopropiedad que contiene a todos sus oyentes. E incluso funciona para eliminar oyentes con funciones anónimas.

Ivan Castellanos
fuente
44
Este método no funcionará si está escribiendo un script de usuario o un script de contenido. No solo es probable que estén encerrados en estos días, sino ¿cómo puede garantizar el orden de ejecución?
huyz
Este método funciona con Chrome / 19 y FF / 12. se puede garantizar el orden de ejecución si
conecta
8
¿No podrías simplemente modificar en su Node.prototypelugar? Ahí es donde HTMLAnchorElementhereda .addEventListenerde todos modos.
Esailija
3
Asume que el agente de usuario implementa la herencia de prototipo para los objetos del host DOM y le permite modificarlos. Ninguna de esas son buenas ideas: no modifique objetos que no le pertenecen .
RobG
1
Esto es bastante inútil: muestra qué EventListeners se agregaron, lo que puede ser bueno en algunos casos, pero no lo que se eliminó. Entonces, si está intentando depurar lo que se eliminó y lo que no, simplemente no hay forma.
gotofritz
52

Use getEventListeners en Google Chrome :

getEventListeners(document.getElementByID('btnlogin'));
getEventListeners($('#btnlogin'));
Pranay Soni
fuente
1
Error de referencia no capturado: getEventListeners no está definido
Bryan Grace
3
getEventListeners solo funciona en la consola de devtools de Chrome. Y este es un duplicado de esta respuesta más elaborada: stackoverflow.com/a/16544813/1026
Nickolay
41

(Reescribiendo la respuesta de esta pregunta ya que es relevante aquí).

Al depurar, si solo desea ver los eventos, le recomiendo ...

  1. Evento visual
  2. La sección Elementos de las Herramientas para desarrolladores de Chrome: seleccione un elemento y busque "Oyentes de eventos" en la parte inferior derecha (similar en Firefox)

Si desea usar los eventos en su código y está usando jQuery antes de la versión 1.8 , puede usar:

$(selector).data("events")

para obtener los eventos. A partir de la versión 1.8, se descontinúa el uso de .data ("eventos") (consulte este ticket de error ). Puedes usar:

$._data(element, "events")

Otro ejemplo: escriba todos los eventos de clic en un determinado enlace a la consola:

var $myLink = $('a.myClass');
console.log($._data($myLink[0], "events").click);

(Ver http://jsfiddle.net/HmsQC/ para un ejemplo de trabajo)

Desafortunadamente, el uso de $ ._ data no se recomienda, excepto para la depuración, ya que es una estructura interna de jQuery y podría cambiar en futuras versiones. Lamentablemente, no conozco ningún otro medio fácil para acceder a los eventos.

Luke
fuente
1
$._data(elem, "events")no parece funcionar con jQuery 1.10, al menos para eventos registrados usando $(elem).on('event', ...). ¿Alguien sabe cómo depurarlos?
Marius Gedminas
44
No soy positivo, pero creo que el primer parámetro de $._datapuede ser un elemento y no un objeto jQuery. Así que necesito arreglar mi ejemplo anterior para serconsole.log($._data($myLink[0], "events").click);
Lucas
29

1: Prototype.observeutiliza Element.addEventListener (ver el código fuente )

2: Puede anular Element.addEventListenerpara recordar los oyentes agregados ( EventListenerListse eliminó la propiedad práctica de la propuesta de especificación DOM3). Ejecute este código antes de adjuntar cualquier evento:

(function() {
  Element.prototype._addEventListener = Element.prototype.addEventListener;
  Element.prototype.addEventListener = function(a,b,c) {
    this._addEventListener(a,b,c);
    if(!this.eventListenerList) this.eventListenerList = {};
    if(!this.eventListenerList[a]) this.eventListenerList[a] = [];
    this.eventListenerList[a].push(b);
  };
})();

Lea todos los eventos por:

var clicks = someElement.eventListenerList.click;
if(clicks) clicks.forEach(function(f) {
  alert("I listen to this function: "+f.toString());
});

Y no olvide anular Element.removeEventListenerpara eliminar el evento de la costumbre Element.eventListenerList.

3: la Element.onclickpropiedad necesita cuidados especiales aquí:

if(someElement.onclick)
  alert("I also listen tho this: "+someElement.onclick.toString());

4: no olvide el Element.onclickatributo de contenido: estas son dos cosas diferentes:

someElement.onclick = someHandler; // IDL attribute
someElement.setAttribute("onclick","otherHandler(event)"); // content attribute

Así que también debes manejarlo:

var click = someElement.getAttribute("onclick");
if(click) alert("I even listen to this: "+click);

El marcador de evento visual (mencionado en la respuesta más popular) solo roba el caché del controlador de la biblioteca personalizada:

Resulta que no hay un método estándar proporcionado por la interfaz DOM recomendada por el W3C para averiguar qué oyentes de eventos están conectados a un elemento en particular. Si bien esto puede parecer un descuido, hubo una propuesta para incluir una propiedad llamada eventListenerList en la especificación DOM de nivel 3, pero desafortunadamente se eliminó en borradores posteriores. Como tal, nos vemos obligados a mirar las bibliotecas Javascript individuales, que generalmente mantienen una memoria caché de eventos adjuntos (para que luego puedan eliminarse y realizar otras abstracciones útiles).

Como tal, para que Visual Event muestre eventos, debe poder analizar la información del evento de una biblioteca Javascript.

La anulación de elementos puede ser cuestionable (es decir, porque hay algunas características específicas del DOM, como colecciones en vivo, que no se pueden codificar en JS), pero le da soporte al eventListenerList de forma nativa y funciona en Chrome, Firefox y Opera (no funciona en IE7 )

Jan Turoň
fuente
23

Puede ajustar los métodos DOM nativos para administrar oyentes de eventos colocando esto en la parte superior de su <head>:

<script>
    (function(w){
        var originalAdd = w.addEventListener;
        w.addEventListener = function(){
            // add your own stuff here to debug
            return originalAdd.apply(this, arguments);
        };

        var originalRemove = w.removeEventListener;
        w.removeEventListener = function(){
            // add your own stuff here to debug
            return originalRemove.apply(this, arguments);
        };
    })(window);
</script>

H / T @ les2

Jon z
fuente
Corríjame si me equivoco, pero ¿no es solo para los oyentes de eventos conectados a la ventana?
Maciej Krawczyk
@MaciejKrawczyk sí, y aquellos que burbujean
Jon z
18

Las herramientas para desarrolladores de Firefox ahora hacen esto. Los eventos se muestran haciendo clic en el botón "ev" a la derecha de la pantalla de cada elemento, incluidos los eventos jQuery y DOM .

Captura de pantalla del botón de escucha de eventos de las herramientas de desarrollo de Firefox en la pestaña del inspector

simonzack
fuente
Observé uno de los elementos dom en la página en los que el marcador de Visual Event 2 muestra un evento conectado, y vi el pequeño botón "ev" a la derecha, como muestra.
Eric
El oyente de eventos de Firefox podría descubrir eventos delegados de jQuery mientras Chrome necesita un complemento para hacerlo.
Nick Tsai
12

Si tiene Firebug , puede usarlo console.dir(object or array)para imprimir un árbol agradable en el registro de la consola de cualquier escalar, matriz u objeto de JavaScript.

Tratar:

console.dir(clickEvents);

o

console.dir(window);
Michael Butler
fuente
99
Nueva función en firebug 1.12 getfirebug.com/wiki/index.php/GetEventListeners
Javier Constanzo
10

Solución totalmente funcional basada en la respuesta de Jan Turon : se comporta como getEventListeners()desde la consola:

(Hay un pequeño error con duplicados. De todos modos, no se rompe mucho).

(function() {
  Element.prototype._addEventListener = Element.prototype.addEventListener;
  Element.prototype.addEventListener = function(a,b,c) {
    if(c==undefined)
      c=false;
    this._addEventListener(a,b,c);
    if(!this.eventListenerList)
      this.eventListenerList = {};
    if(!this.eventListenerList[a])
      this.eventListenerList[a] = [];
    //this.removeEventListener(a,b,c); // TODO - handle duplicates..
    this.eventListenerList[a].push({listener:b,useCapture:c});
  };

  Element.prototype.getEventListeners = function(a){
    if(!this.eventListenerList)
      this.eventListenerList = {};
    if(a==undefined)
      return this.eventListenerList;
    return this.eventListenerList[a];
  };
  Element.prototype.clearEventListeners = function(a){
    if(!this.eventListenerList)
      this.eventListenerList = {};
    if(a==undefined){
      for(var x in (this.getEventListeners())) this.clearEventListeners(x);
        return;
    }
    var el = this.getEventListeners(a);
    if(el==undefined)
      return;
    for(var i = el.length - 1; i >= 0; --i) {
      var ev = el[i];
      this.removeEventListener(a, ev.listener, ev.useCapture);
    }
  };

  Element.prototype._removeEventListener = Element.prototype.removeEventListener;
  Element.prototype.removeEventListener = function(a,b,c) {
    if(c==undefined)
      c=false;
    this._removeEventListener(a,b,c);
      if(!this.eventListenerList)
        this.eventListenerList = {};
      if(!this.eventListenerList[a])
        this.eventListenerList[a] = [];

      // Find the event in the list
      for(var i=0;i<this.eventListenerList[a].length;i++){
          if(this.eventListenerList[a][i].listener==b, this.eventListenerList[a][i].useCapture==c){ // Hmm..
              this.eventListenerList[a].splice(i, 1);
              break;
          }
      }
    if(this.eventListenerList[a].length==0)
      delete this.eventListenerList[a];
  };
})();

Uso:

someElement.getEventListeners([name]) - lista de retorno de oyentes de eventos, si se establece el nombre, devuelve la matriz de oyentes para ese evento

someElement.clearEventListeners([name]) - eliminar todos los oyentes de eventos, si el nombre está configurado solo eliminar oyentes para ese evento

n00b
fuente
3
Esta es una buena respuesta, pero no puedo hacer que funcione en los elementos bodyy document.
David Bradshaw
Muy buena solución!
Dzhakhar Ukhaev
@ Peter Mortensen, ¿por qué cambiaste el nombre de Jan? :)
n00b
1
@David Bradshaw: porque el cuerpo, el documento y la ventana tienen su propio prototipo y no el prototipo de Element ...
Stefan Steiger
1
@ n00b: hay un error. Por eso hmmm. Utiliza el operador de coma en la instrucción if para listener y useCaption ... El operador de coma evalúa cada uno de sus operandos (de izquierda a derecha) y devuelve el valor del último operando. Entonces, eliminas el primer eventListener que coincide con useCapture, independientemente del tipo de evento ... Eso debería ser && en lugar de coma.
Stefan Steiger
8

Opera 12 (no es el último motor Chrome Webkit basado) Dragonfly ha tenido esto por un tiempo y obviamente se muestra en la estructura DOM. En mi opinión, es un depurador superior y es la única razón por la que sigo usando la versión basada en Opera 12 (no hay una versión v13, v14 y el v15 Webkit todavía carece de Dragonfly)

ingrese la descripción de la imagen aquí

Daniel Sokolowski
fuente
4

Prototipo 1.7.1 camino

function get_element_registry(element) {
    var cache = Event.cache;
    if(element === window) return 0;
    if(typeof element._prototypeUID === 'undefined') {
        element._prototypeUID = Element.Storage.UID++;
    }
    var uid =  element._prototypeUID;           
    if(!cache[uid]) cache[uid] = {element: element};
    return cache[uid];
}
Ronald
fuente
4

Recientemente estuve trabajando con eventos y quería ver / controlar todos los eventos en una página. Después de buscar posibles soluciones, decidí seguir mi propio camino y crear un sistema personalizado para monitorear eventos. Entonces, hice tres cosas.

Primero, necesitaba un contenedor para todos los oyentes de eventos en la página: ese es el EventListenersobjeto. Tiene tres métodos útiles: add(), remove(), y get().

A continuación, crea un EventListenerobjeto para mantener la información necesaria para el evento, es decir: target, type, callback, options, useCapture, wantsUntrusted, y añade un método remove()para eliminar el oyente.

Por último, extendí el nativo addEventListener()y los removeEventListener()métodos para que funcionen con los objetos que he creado ( EventListenery EventListeners).

Uso:

var bodyClickEvent = document.body.addEventListener("click", function () {
    console.log("body click");
});

// bodyClickEvent.remove();

addEventListener()crea un EventListenerobjeto, lo agrega EventListenersy devuelve el EventListenerobjeto, para que pueda eliminarse más tarde.

EventListeners.get()se puede usar para ver a los oyentes en la página. Acepta una EventTargeto una cadena (tipo de evento).

// EventListeners.get(document.body);
// EventListeners.get("click");

Manifestación

Digamos que queremos conocer a todos los oyentes de eventos en esta página actual. Podemos hacer eso (suponiendo que esté utilizando una extensión del administrador de scripts, Tampermonkey en este caso). El siguiente script hace esto:

// ==UserScript==
// @name         New Userscript
// @namespace    http://tampermonkey.net/
// @version      0.1
// @description  try to take over the world!
// @author       You
// @include      https://stackoverflow.com/*
// @grant        none
// ==/UserScript==

(function() {
    fetch("https://raw.githubusercontent.com/akinuri/js-lib/master/EventListener.js")
        .then(function (response) {
            return response.text();
        })
        .then(function (text) {
            eval(text);
            window.EventListeners = EventListeners;
        });
})(window);

Y cuando enumeramos todos los oyentes, dice que hay 299 oyentes de eventos. "Parece" que hay algunos duplicados, pero no sé si realmente son duplicados. No todos los tipos de eventos están duplicados, por lo que todos esos "duplicados" pueden ser un oyente individual.

captura de pantalla de la consola que enumera todos los oyentes de eventos en esta página

El código se puede encontrar en mi repositorio. No quería publicarlo aquí porque es bastante largo.


Actualización: Esto no parece funcionar con jQuery. Cuando examino EventListener, veo que la devolución de llamada es

function(b){return"undefined"!=typeof r&&r.event.triggered!==b.type?r.event.dispatch.apply(a,arguments):void 0}

Creo que esto pertenece a jQuery, y no es la devolución de llamada real. jQuery almacena la devolución de llamada real en las propiedades de EventTarget:

$(document.body).click(function () {
    console.log("jquery click");
});

ingrese la descripción de la imagen aquí

Para eliminar un detector de eventos, la devolución de llamada real debe pasarse al removeEventListener()método. Entonces, para que esto funcione con jQuery, necesita más modificaciones. Podría arreglar eso en el futuro.

akinuri
fuente
¡Funciona genial! Gracias
mt025
3

Estoy tratando de hacer eso en jQuery 2.1, y con el $().click() -> $(element).data("events").click;método " " no funciona.

Me di cuenta de que solo las funciones $ ._ data () funcionan en mi caso:

	$(document).ready(function(){

		var node = $('body');
		
        // Bind 3 events to body click
		node.click(function(e) { alert('hello');  })
			.click(function(e) { alert('bye');  })
			.click(fun_1);

        // Inspect the events of body
		var events = $._data(node[0], "events").click;
		var ev1 = events[0].handler // -> function(e) { alert('hello')
		var ev2 = events[1].handler // -> function(e) { alert('bye')
		var ev3 = events[2].handler // -> function fun_1()
        
		$('body')
			.append('<p> Event1 = ' + eval(ev1).toString() + '</p>')
			.append('<p> Event2 = ' + eval(ev2).toString() + '</p>')
			.append('<p> Event3 = ' + eval(ev3).toString() + '</p>');        
	
	});

	function fun_1() {
		var txt = 'text del missatge';	 
		alert(txt);
	}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>

<body>
</body>

Joel Barba
fuente
1

cambiar estas funciones le permitirá registrar los oyentes agregados:

EventTarget.prototype.addEventListener
EventTarget.prototype.attachEvent
EventTarget.prototype.removeEventListener
EventTarget.prototype.detachEvent

leer al resto de los oyentes con

console.log(someElement.onclick);
console.log(someElement.getAttribute("onclick"));
Aiden Waterman
fuente