jQuery encuentra controladores de eventos registrados con un objeto

555

Necesito encontrar qué controladores de eventos están registrados sobre un objeto.

Por ejemplo:

$("#el").click(function() {...});
$("#el").mouseover(function() {...});

$("#el")tiene clic y mouseover registrados.

¿Hay una función para descubrir eso y posiblemente iterar sobre los controladores de eventos?

Si no es posible en un objeto jQuery a través de métodos adecuados, ¿es posible en un objeto DOM simple?

edades04
fuente
55
desafortunadamente, ahora: bugs.jquery.com/ticket/10589
Skylar Saveland
2
admite jQuery pre y post 1.8:var events = (jQuery._data || jQuery.data)(elem, 'events');
oriadam
2
Tenga en cuenta que puede usar las herramientas de desarrollo FF y Chrome (la F12) para ver estos oyentes de eventos. Ver developers.google.com/web/tools/chrome-devtools/debug/… y developer.mozilla.org/en-US/docs/Tools/Page_Inspector/How_to/…
oriadam

Respuestas:

691

A partir de jQuery 1.8, los datos del evento ya no están disponibles en la "API pública" para los datos. Lea esta publicación de blog jQuery . Ahora deberías usar esto en su lugar:

jQuery._data( elem, "events" );

elem debe ser un elemento HTML, no un objeto jQuery o selector.

Tenga en cuenta que esta es una estructura interna 'privada' y no debe modificarse. Use esto solo para propósitos de depuración.

En versiones anteriores de jQuery, es posible que deba usar el método anterior, que es:

jQuery( elem ).data( "events" );
jps
fuente
222
pero aún puedes usar $._data($(elem).get(0), "events")
bullgare
10
blog.jquery.com/2011/11/08/building-a-slimmer-jquery .data ("eventos"): jQuery almacena sus datos relacionados con eventos en un objeto de datos llamado (espere) eventos en cada elemento. Esta es una estructura de datos interna, por lo que en 1.8 se eliminará del espacio de nombre de datos de usuario para que no entre en conflicto con elementos del mismo nombre. Aún se puede acceder a los datos de eventos de jQuery a través de jQuery._data (elemento, "eventos"), pero tenga en cuenta que esta es una estructura de datos interna que no está documentada y no debe modificarse.
Sam Greenhalgh
He estado usando este método para intentar descubrir el evento de clic de un botón. En la consola de Chrome se mostró handler: function () {dentro de la propiedad de clic. Tuve que hacer doble clic en la parte de la función para que se expandiera y mostrara el contenido completo de la función.
Jim
@jim sí, el doble clic es la respuesta
Adib Aroui
2
Soporta perfectamente ambas opciones:var events = (jQuery._data || jQuery.data)(elem, 'events');
oriadam
84

Puede hacerlo rastreando los eventos (a partir de jQuery 1.8+), de esta manera:

$.each($._data($("#id")[0], "events"), function(i, event) {
  // i is the event type, like "click"
  $.each(event, function(j, h) {
    // h.handler is the function being called
  });
});

Aquí hay un ejemplo con el que puedes jugar:

$(function() {
  $("#el").click(function(){ alert("click"); });
  $("#el").mouseover(function(){ alert("mouseover"); });

  $.each($._data($("#el")[0], "events"), function(i, event) {
    output(i);
    $.each(event, function(j, h) {
        output("- " + h.handler);
    });
  });
});

function output(text) {
    $("#output").html(function(i, h) {
        return h + text + "<br />";
    });
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<div id="el">Test</div>
<code>
    <span id="output"></span>
</code>

Nick Craver
fuente
2
Esta es la respuesta correcta. La parte crucial es la segunda mitad. Esto me ayudó a encontrar un problema en menos de un minuto que me hubiera llevado más de una hora si tuviera que buscar en todo el código. ¡Gracias!
Andrew Ensley
2
Funciona con 1.4, pero no en jQuery 1.8.2.
Timo Kähkönen
15
Para jQuery 1.8+, se debe utilizar el método de 'datos privados': jQuery._data( jQuery("#el")[0], "events" );en lugar del método "datos públicos: jQuery("#el").data("events"). El eventsobjeto no se ha almacenado .data()durante mucho tiempo, recortamos algunos bytes de código eliminando este "proxy" de la "API pública"
gnarf
36

Para jQuery 1.8+, esto ya no funcionará porque los datos internos se colocan en un objeto diferente.

La última forma no oficial (pero también funciona en versiones anteriores, al menos en 1.7.2) de hacerlo ahora es: $._data(element, "events")

El guión bajo ("_") es lo que hace la diferencia aquí. Internamente, está llamando $.data(element, name, null, true), el último (cuarto) parámetro es interno ("pvt").

PhistucK
fuente
$ ._ data ("cuerpo", "eventos") undefined $ (). jquery; "1.7.1" (intentado 1.7.2 y 1.8.1 todo el tiempo "indefinido")
Mars Robertson
2
@Michal - api.jquery.com/jQuery.data dice que acepta un elemento, no un selector.
PhistucK
1
Ahora funciona bien: $ ._ data ($ ("body"). Get (0), "events") O incluso mejor: $ ("body"). Data ("events")!
Mars Robertson el
2
FWIW: señalar que "internamente" llama a la otra función de datos con un parámetro que específicamente no documentamos probablemente no sea necesario. Pero sí, jQuery._data( element, "events" )es la forma 'correcta' de obtener esta información ahora.
Gnarf
34

Enchufe descarado, pero puede usar findHandlerJS

Para usarlo solo debe incluir findHandlersJS (o simplemente copiar y pegar el código JavaScript sin formato en la ventana de la consola de Chrome) y especificar el tipo de evento y un selector jquery para los elementos que le interesan.

Para su ejemplo, puede encontrar rápidamente los controladores de eventos que mencionó haciendo

findEventHandlers("click", "#el")
findEventHandlers("mouseover", "#el")

Esto es lo que se devuelve:

  • elemento
    El real donde se registró el controlador de eventos
  • Eventos
    Matriz con información sobre los controladores de eventos jquery para el tipo de evento que nos interesa (por ejemplo, hacer clic, cambiar, etc.)
    • manejador
      Método del manejador de eventos real que puede ver haciendo clic derecho y seleccionando Mostrar definición de función
    • selector
      El selector proporcionado para eventos delegados. Estará vacío para eventos directos.
    • destinos
      Lista con los elementos a los que se dirige este controlador de eventos. Por ejemplo, para un controlador de eventos delegado que está registrado en el objeto del documento y se dirige a todos los botones de una página, esta propiedad enumerará todos los botones de la página. Puede desplazarlos y verlos resaltados en cromo.

Puedes probarlo aquí

Rui
fuente
Creo que esa debería ser la respuesta aceptada. Ese es el único que también funciona para mí. Es muy exhaustivo ya que pasa por todos los elementos en busca de eventos.
Marquinho Peli
12

Yo uso eventbug plugin para Firebug para este propósito.

Anton
fuente
Gracias, buen consejo. La extensión agrega una pestaña a Firebug ("Eventos") que muestra los eventos de la página, para que pueda extraerlos fácilmente.
Gruber
11
Además, Chrome Developer Tools tiene "Oyentes de eventos" en la pestaña "Elementos" y "Puntos de interrupción de escucha de eventos" en la pestaña "Fuentes".
clayzermk1
10

Combiné ambas soluciones de @jps a una función:

jQuery.fn.getEvents = function() {
    if (typeof(jQuery._data) === 'function') {
        return jQuery._data(this.get(0), 'events') || {};
    }

    // jQuery version < 1.7.?
    if (typeof(this.data) === 'function') {
        return this.data('events') || {};
    }

    return {};
};

Pero cuidado, esta función solo puede devolver eventos que se configuraron usando jQuery.

algoritmo
fuente
5

A partir de 1.9 no existe una forma documentada de recuperar los eventos, aparte de usar el complemento Migrate para restaurar el comportamiento anterior. Podría usar el método _.data () como menciona jps, pero ese es un método interno. Entonces, haga lo correcto y use el complemento Migrate si necesita esta funcionalidad.

De la documentación de jQuery en .data("events")

Antes de 1.9, .data ("eventos") podía usarse para recuperar la estructura de datos de eventos internos no documentados de jQuery para un elemento si ningún otro código había definido un elemento de datos con el nombre "eventos". Este caso especial se ha eliminado en 1.9. No hay una interfaz pública para recuperar esta estructura de datos interna, y permanece sin documentar. Sin embargo, el complemento jQuery Migrate restaura este comportamiento para el código que depende de él.

oligofren
fuente
La respuesta aceptada también muestra claramente la nueva forma correcta de obtenerla para las versiones recientes: jQuery._data( elem, "events" );...
Ian
2
Una forma privada e indocumentada nunca será una forma correcta . La forma correcta, es decir, documentada, pública e intencionada, es usar el complemento Migrate.
oligofren
3
Parece que no entiendes el punto del complemento Migrate. jQuery eliminó las funciones obsoletas, y el complemento Migrate es para ayudar a migrar el código del desarrollador a las versiones más nuevas para que puedan aprovechar de inmediato las nuevas funciones y mejoras, pero no perder la funcionalidad. Su objetivo es ayudar al codificador a ver lo que deben hacer para comenzar a usar correctamente nuevas versiones de jQuery. No debe usarlo en producción para restaurar funciones. Además, muchas cosas no están documentadas y actualizadas en la documentación de jQuery, ya lo han señalado antes, por lo que no es una razón
Ian
Además, si se incluye como una sugerencia en el blog jQuery, lo usaría: blog.jquery.com/2012/08/09/jquery-1-8-released
Ian
Su razonamiento en el complemento Migrate parece razonable. ¿Aceptar si borro mi respuesta?
oligofren
5

Para verificar eventos en un elemento:

var events = $._data(element, "events")

Tenga en cuenta que esto solo funcionará con controladores de eventos directos, si está utilizando $ (document) .on ("event-name", "jq-selector", function () {// logic}), querrá ver el La función getEvents se encuentra al final de esta respuesta

Por ejemplo:

 var events = $._data(document.getElementById("myElemId"), "events")

o

 var events = $._data($("#myElemId")[0], "events")

Ejemplo completo:

<html>
    <head>
        <script src="//ajax.googleapis.com/ajax/libs/jquery/1.8.0/jquery.min.js" type="text/javascript"></script>
        <script>
            $(function() {
                $("#textDiv").click(function() {
                    //Event Handling
                });
                var events = $._data(document.getElementById('textDiv'), "events");
                var hasEvents = (events != null);
            });
        </script>
    </head>
    <body>
        <div id="textDiv">Text</div>
    </body>
</html>

Una forma más completa de verificar, que incluye escuchas dinámicos, instalados con $ (documento) .on

function getEvents(element) {
    var elemEvents = $._data(element, "events");
    var allDocEvnts = $._data(document, "events");
    for(var evntType in allDocEvnts) {
        if(allDocEvnts.hasOwnProperty(evntType)) {
            var evts = allDocEvnts[evntType];
            for(var i = 0; i < evts.length; i++) {
                if($(element).is(evts[i].selector)) {
                    if(elemEvents == null) {
                        elemEvents = {};
                    }
                    if(!elemEvents.hasOwnProperty(evntType)) {
                        elemEvents[evntType] = [];
                    }
                    elemEvents[evntType].push(evts[i]);
                }
            }
        }
    }
    return elemEvents;
}

Ejemplo de uso:

getEvents($('#myElemId')[0])
Tom G
fuente
Este getEventsmétodo es extremadamente bueno, pero lo cierto es que proporciona entradas duplicadas en IE11 (lo sé, IE nuevamente, pero la empresa lo necesita ...). EDITAR: $ ._ datos contiene eventos duplicados para el elemento, aunque en FF no contiene ninguno ... Mundo extraño de IE. Pero es importante tener cuidado con esta posibilidad de tener eventos duplicados.
Dominik Szymański
Oh, Tom, en realidad es tu código el que multiplica los eventos con cada ejecución de este método. No está bien.
Dominik Szymański
4

Creé un selector de jQuery personalizado que verifica tanto el caché de jQuery de los controladores de eventos asignados como los elementos que usan el método nativo para agregarlos:

(function($){

    $.find.selectors[":"].event = function(el, pos, match) {

        var search = (function(str){
            if (str.substring(0,2) === "on") {str = str.substring(2);}
            return str;
        })(String(match[3]).trim().toLowerCase());

        if (search) {
            var events = $._data(el, "events");
            return ((events && events.hasOwnProperty(search)) || el["on"+search]);
        }

        return false;

    };

})(jQuery);

Ejemplo:

$(":event(click)")

Esto devolverá elementos que tienen un controlador de clic adjunto.

Erutan409
fuente
2

En un navegador moderno con ECMAScript 5.1 / Array.prototype.map, también puede usar

jQuery._data(DOCUMENTELEMENT,'events')["EVENT_NAME"].map(function(elem){return elem.handler;});

en la consola de su navegador, que imprimirá la fuente de los controladores, delimitada por comas. Útil para echar un vistazo a lo que se está ejecutando en un evento en particular.

Jesan Fafon
fuente
jQuery._data('ct100_ContentPlaceHolder1_lcsSection','events')["EVENT_NAME"].map(function(elem){return elem.handler;}); Error de tipo no capturado: no se puede leer la propiedad 'EVENT_NAME' de undefined en <anónimo>: 1: 62
Mike W
'ct100_ContentPlaceHolder1_lcsSection'es una cadena, no un elemento DOM.
Jesan Fafon
2

Los eventos se pueden recuperar usando:

jQuery(elem).data('events');

o jQuery 1.8+:

jQuery._data(elem, 'events');

Nota: Los eventos delimitados usando $('selector').live('event', handler) se pueden recuperar usando:

jQuery(document).data('events')
R. Oosterholt
fuente
2
jQuery (document) .data ('eventos') me da indefinido
Mike W
1

Tengo que decir que muchas de las respuestas son interesantes, pero recientemente tuve un problema similar y la solución fue extremadamente simple siguiendo el camino del DOM. Es diferente porque no itera sino que apunta directamente al evento que necesita, pero a continuación le daré una respuesta más general.

Tenía una imagen en una fila:

<table>
  <td><tr><img class="folder" /></tr><tr>...</tr></td>
</table>

Y esa imagen tenía un controlador de eventos de clic adjunto:

imageNode.click(function () { ... });

Mi intención era expandir el área en la que se podía hacer clic a toda la fila, por lo que primero obtuve todas las imágenes y filas relativas:

tableNode.find("img.folder").each(function () {
  var tr;

  tr = $(this).closest("tr");
  // <-- actual answer
});

Ahora, en la línea de respuesta real , acabo de hacer lo siguiente, respondiendo a la pregunta original:

tr.click(this.onclick);

Así que busqué el controlador de eventos directamente desde el elemento DOM y lo puse en el controlador de eventos jQuery click. Funciona de maravilla.

Ahora, al caso general. En los viejos días anteriores a jQuery, se podían conectar todos los eventos a un objeto con dos funciones simples pero poderosas que Douglas Crockford nos regaló a los mortales :

function walkTheDOM(node, func)
{
  func(node);
  node = node.firstChild;
  while (node)
  {
    walkTheDOM(node, func);
    node = node.nextSibling;
  }
}

function purgeEventHandlers(node)
{
  walkTheDOM(node, function (n) {
    var f;

    for (f in n)
    {
      if (typeof n[f] === "function")
      {
        n[f] = null;
      }
    }
  });
}
pid
fuente
0

Otra forma de hacerlo es usar jQuery para tomar el elemento, luego pasar por Javascript real para obtener, configurar y jugar con los controladores de eventos. Por ejemplo:

var oldEventHandler = $('#element')[0].onclick;
// Remove event handler
$('#element')[0].onclick = null;
// Switch it back
$('#element')[0].onclick = oldEventHandler;
tempranova
fuente
1
Creo que jQuery hace la optimización de manejo de eventos que creo que es eludido por su código aquí.
Emile Bergeron
Gracias, sí, tuve la sensación de que esto era hacky, ¿algún buen enlace para aprender más sobre esa optimización?
tempranova
0

Combiné algunas de las respuestas anteriores y creé este script de aspecto loco pero funcional que, con suerte, enumera a la mayoría de los oyentes de eventos en el elemento dado. Siéntase libre de optimizarlo aquí.

var element = $("#some-element");

// sample event handlers
element.on("mouseover", function () {
  alert("foo");
});

$(".parent-element").on("mousedown", "span", function () {
  alert("bar");
});

$(document).on("click", "span", function () {
  alert("xyz");
});

var collection = element.parents()
  .add(element)
  .add($(document));
collection.each(function() {
  var currentEl = $(this) ? $(this) : $(document);
  var tagName = $(this)[0].tagName ? $(this)[0].tagName : "DOCUMENT";
  var events = $._data($(this)[0], "events");
  var isItself = $(this)[0] === element[0]
  if (!events) return;
  $.each(events, function(i, event) {
    if (!event) return;
    $.each(event, function(j, h) {
      var found = false;        
      if (h.selector && h.selector.length > 0) {
        currentEl.find(h.selector).each(function () {
          if ($(this)[0] === element[0]) {
            found = true;
          }
        });
      } else if (!h.selector && isItself) {
        found = true;
      }

      if (found) {
        console.log("################ " + tagName);
        console.log("event: " + i);
        console.log("selector: '" + h.selector + "'");
        console.log(h.handler);
      }
    });
  });
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>

<div class="parent-element">
  <span id="some-element"></span>
</div>

Marek Lisý
fuente
0

jQuery no le permite simplemente acceder a los eventos de un elemento determinado. Puede acceder a ellos utilizando un método interno no documentado

$._data(element, "events")

Pero todavía no le dará todos los eventos, para ser precisos, no le mostrará los eventos asignados con

$([selector|element]).on()

Estos eventos se almacenan dentro del documento, por lo que puede buscarlos navegando a través de

$._data(document, "events")

pero eso es un trabajo duro, ya que hay eventos para toda la página web.

Tom G creó una función que filtra el documento solo para eventos de un elemento dado y combina la salida de ambos métodos, pero tenía un defecto de duplicar eventos en la salida (y efectivamente en la lista de eventos internos de jQuery del elemento que interfiere con su aplicación). Arreglé esa falla y puedes encontrar el código a continuación. Simplemente péguelo en su consola de desarrollo o en el código de su aplicación y ejecútelo cuando sea necesario para obtener una buena lista de todos los eventos para un elemento dado.

Es importante notar que el elemento es en realidad HTMLElement, no un objeto jQuery.

function getEvents(element) {
    var elemEvents = $._data(element, "events");
    var allDocEvnts = $._data(document, "events");
    function equalEvents(evt1, evt2)
    {
        return evt1.guid === evt2.guid;
    }

    for(var evntType in allDocEvnts) {
        if(allDocEvnts.hasOwnProperty(evntType)) {
            var evts = allDocEvnts[evntType];
            for(var i = 0; i < evts.length; i++) {
                if($(element).is(evts[i].selector)) {
                    if(elemEvents == null) {
                        elemEvents = {};
                    }
                    if(!elemEvents.hasOwnProperty(evntType)) {
                        elemEvents[evntType] = [];
                    }
                    if(!elemEvents[evntType].some(function(evt) { return equalEvents(evt, evts[i]); })) {
                        elemEvents[evntType].push(evts[i]);
                    }
                }
            }
        }
    }
    return elemEvents;
}
Dominik Szymański
fuente