Como encontrar ouvintes de eventos em um nó DOM ao depurar ou no código JavaScript?

841

Eu tenho uma página onde alguns ouvintes de eventos estão anexados às caixas de entrada e caixas de seleção. Existe uma maneira de descobrir quais ouvintes de eventos estão observando um nó DOM específico e para qual evento?

Os eventos são anexados usando:

  1. Protótipos Event.observe ;
  2. DOM's addEventListener;
  3. Como atributo do elemento element.onclick.
Navneet
fonte
3
Como os eventos são anexados em primeiro lugar? Você está usando uma biblioteca (por exemplo, Prototype, jQuery, etc)?
Crescent Fresh
É importante observar que várias funções de retorno de chamada podem ser anexadas para o mesmo tipo de evento via element.addEventListener(type, callback, [bubble]), enquanto element.onclick = functionsobrescrevem toda vez que você atribuir.
Braden Best

Respostas:

507

Se você apenas precisar inspecionar o que está acontecendo em uma página, tente o bookmarklet Visual Event .

Atualização : Evento Visual 2 disponível.

Andrew Hedges
fonte
1
Perfeito! Bate o EventBugplugin do Firebug .
Robin Maben
22
Isso adiciona um milhão de elementos à página, muitos dos quais são imagens. Sua utilidade é bastante reduzida em uma página com muitos manipuladores de eventos (a minha tem 17k e o Visual Event levou cerca de 3 minutos para carregar).
Tony R
15
Eu acredito que o @ssharma extensão do Chrome mencionado é o único disponível aqui
kmote
1
Evento Visial não funcionará se a página referir uma biblioteca js de terceiros. Isso gera um erro: XMLHttpRequest não pode carregar A.com/js/jquery-ui-1.10.3.custom.js?_=1384831682813 . A origem B.com não é permitida pelo Access-Control-Allow-Origin.
hiway
5
As Ferramentas de desenvolvedor internas do Chrome e do FF (F12) têm um visualizador de eventos interno! Chrome: na guia "Elementos", selecione e elemento e veja à direita: Estilo, Computado, Ouvintes de eventos
oriadam
365

Depende de como os eventos estão anexados. Para supor ilustração, temos o seguinte manipulador de cliques:

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

Vamos anexá-lo ao nosso elemento usando métodos diferentes, alguns que permitem inspeção e outros que não.

Método A) manipulador de eventos único

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

Método B) manipuladores de vários eventos

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 (armazena o manipulador dentro de um 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
    })

(Veja jQuery.fn.datae jQuery.data)

Método D): Protótipo (confuso)

$(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 (ficou muito difícil aqui)

    // 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 (pouco melhor)

    // inspect
    var clickEvents = element.getStorage().get('prototype_event_registry').get('click');
    clickEvents.each(function(wrapper){
        alert(wrapper.handler) // alerts "function() { alert('clicked!') }"
    })
Crescent Fresh
fonte
3
Obrigado por atualizar isso. É uma pena que você precise percorrer cada tipo de manipulador.
Keith Bentrup 14/09/09
4
No "Método B" (addEventListener), aqui está uma resposta sobre o status dos recursos de enumeração para manipuladores registrados com a API de eventos DOM pura: stackoverflow.com/questions/7810534/…
Nickolay
@ John: obrigado pelo comentário. Você tem um exemplo de "JavaScript real" para buscar ouvintes adicionados anteriormente via addEventListener?
Crescent Fresh
6
@ Jan, isso parece não funcionar para o jQuery 1.7.2, existe uma abordagem diferente para essa versão?
Tomdemuyt 30/05
14
@tomdemuyt No jQuery, os eventos agora são armazenados em uma matriz de dados interna, em vez de serem acessíveis via .data('events')como antes. Para acessar os dados internos do evento, use $._data(elem, 'events'). Observe que essa função está marcada como "somente para uso interno" na fonte do jQuery, portanto, não há promessas de que sempre funcione no futuro, mas acredito que funcionou desde o jQuery 1.7 e ainda funciona.
22614 Matt Browne
351

Suporte getEventListeners(domElement)para Chrome, Firefox, Vivaldi e Safari no console das Ferramentas do desenvolvedor.

Para a maioria dos propósitos de depuração, isso pode ser usado.

Abaixo está uma referência muito boa para usá-lo: https://developers.google.com/web/tools/chrome-devtools/console/utilities#geteventlisteners

Raghav
fonte
1
+1. O código específico do navegador não é um problema para mim, porque estou tentando obter uma página que não controlo para funcionar corretamente.
Grault 19/08/13
9
bom, mas só funciona na linha de comando do console do Chrome :(
gillyspy
5
@Raghav ah graças, o uso é NÃO: como eu pensava :)getEventListeners(object) obj getEventListeners()
jave.web
11
Você poderia ter me poupado 3 horas se tivesse explicado como obter o código-fonte de um ouvinte de evento. Essa "referência" não explica nada. No caso de alguém estava em uma perda aqui está como: var list = getEventListeners(document.getElementById("YOURIDHERE")); console.log(list["focus"][0]["listener"].toString()). Altere "foco" para o evento que você deseja inspecionar e o número se houver vários ouvintes no mesmo evento.
DoABarrelRoll721
46
DICA: getEventListeners($0)obterá os ouvintes do evento para o elemento em que você se concentrou nas ferramentas de desenvolvimento do Chrome. Eu uso isso o tempo todo. O amor não ter que usar funções querySelector ou get__By_
cacoder
91

O WebKit Inspector nos navegadores Chrome ou Safari agora faz isso. Ele exibirá os ouvintes de evento para um elemento DOM quando você o selecionar no painel Elementos.

Ishan
fonte
9
Não tenho certeza se mostra todos os manipuladores de eventos; apenas o único manipulador de eventos HTML.
huyz
3
Devo mencionar EventBug do plugin para o Firebug para a integralidade < softwareishard.com/blog/category/eventbug >
Nickolay
Isso acabou de fazer o meu dia, algum código de protótipo herdado tinha várias funções ligadas ao mesmo evento no mesmo elemento e eu estava morrendo de vontade de tentar localizá-las. Muito obrigado Ishan.
siliconrockstar
70

É possível listar todos os ouvintes de eventos em JavaScript: não é tão difícil; você apenas precisa hackear o prototypemétodo dos elementos HTML ( antes de adicionar os ouvintes).

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});
};

Agora todo elemento âncora ( a) terá uma lastListenerInfopropriedade que contém todos os seus ouvintes. E funciona até para remover ouvintes com funções anônimas.

Ivan Castellanos
fonte
4
Este método não funcionará se você estiver escrevendo um script de usuário ou de conteúdo. Hoje em dia, é provável que você não esteja apenas em área restrita, mas como garantir a ordem de execução?
huyz
esse método funciona com chrome / 19 e FF / 12. ordem de execução pode ser garantida se você ligar antes outros scripts
Tzury Bar Yochay
8
Você não poderia simplesmente modificar Node.prototype? É HTMLAnchorElementdaí que herda .addEventListenerde qualquer maneira.
Esailija
3
Você está assumindo que o agente do usuário implementa a herança de protótipo para objetos de host DOM e permite modificá-los. Nenhuma dessas são boas idéias: não modifique objetos que você não possui .
22812 RobG #
1
Isso é bastante inútil - mostra quais eventListeners foram adicionados, o que pode ser bom em alguns casos, mas não o que foi removido. Portanto, se você estiver tentando depurar o que foi removido e o que não foi, simplesmente não há como.
gotofritz
52

Use getEventListeners no Google Chrome :

getEventListeners(document.getElementByID('btnlogin'));
getEventListeners($('#btnlogin'));
Pranay Soni
fonte
1
Não detectado ReferenceError: getEventListeners não está definido
Bryan Grace
3
O getEventListeners funciona apenas no console de ferramentas do Chrome. E esta é uma duplicata desta resposta mais elaborada: stackoverflow.com/a/16544813/1026
Nickolay 13/02/16
41

(Reescrevendo a resposta desta pergunta, pois ela é relevante aqui.)

Ao depurar, se você quiser apenas ver os eventos, eu recomendo ...

  1. Evento Visual
  2. A seção Elementos das Ferramentas do desenvolvedor do Chrome: selecione um elemento e procure "Ouvintes de eventos" no canto inferior direito (semelhante no Firefox)

Se você quiser usar os eventos em seu código e estiver usando o jQuery antes da versão 1.8 , poderá usar:

$(selector).data("events")

para receber os eventos. A partir da versão 1.8, o uso de .data ("events") é descontinuado (consulte este bilhete de bug ). Você pode usar:

$._data(element, "events")

Outro exemplo: escreva todos os eventos de clique em um determinado link para o console:

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

(veja http://jsfiddle.net/HmsQC/ para um exemplo de trabalho)

Infelizmente, o uso de dados $ ._ não é recomendado, exceto para depuração, pois é uma estrutura interna do jQuery e pode mudar em versões futuras. Infelizmente, não conheço outro meio fácil de acessar os eventos.

Lucas
fonte
1
$._data(elem, "events")parece não funcionar com o jQuery 1.10, pelo menos para eventos registrados usando $(elem).on('event', ...). Alguém sabe como depurar esses?
Marius Gedminas
4
Não sou positivo, mas acho que o primeiro parâmetro de $._datapode precisar ser um elemento e não um objeto jQuery. Então, eu preciso corrigir o meu exemplo acima para ser #console.log($._data($myLink[0], "events").click);
612 Luke Luke
29

1: Prototype.observeusa Element.addEventListener (consulte o código-fonte )

2: Você pode substituir Element.addEventListenerpara lembrar os ouvintes adicionados (a propriedade útil EventListenerListfoi removida da proposta de especificação do DOM3). Execute este código antes que qualquer evento seja anexado:

(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);
  };
})();

Leia todos os eventos por:

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

E não se esqueça de substituir Element.removeEventListenerpara remover o evento da customização Element.eventListenerList.

3: a Element.onclickpropriedade precisa de cuidados especiais aqui:

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

4: não esqueça o Element.onclickatributo content: estas são duas coisas diferentes:

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

Então você precisa lidar com isso também:

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

O bookmarklet Visual Event (mencionado na resposta mais popular) rouba apenas o cache do manipulador de biblioteca personalizado:

Acontece que não há um método padrão fornecido pela interface DOM recomendada pelo W3C para descobrir quais ouvintes de eventos estão conectados a um elemento específico. Embora isso possa parecer uma supervisão, houve uma proposta para incluir uma propriedade chamada eventListenerList na especificação DOM de nível 3, mas infelizmente foi removida em rascunhos posteriores. Como tal, somos forçados a examinar as bibliotecas Javascript individuais, que normalmente mantêm um cache de eventos anexados (para que possam ser removidos posteriormente e executem outras abstrações úteis).

Como tal, para que o Visual Event mostre eventos, ele deve poder analisar as informações do evento de uma biblioteca Javascript.

A substituição de elemento pode ser questionável (ou seja, porque existem alguns recursos específicos do DOM, como coleções ao vivo, que não podem ser codificadas em JS), mas fornece suporte a eventListenerList de forma nativa e funciona no Chrome, Firefox e Opera (não funciona no IE7 )

Jan Turoň
fonte
23

Você pode agrupar os métodos DOM nativos para gerenciar ouvintes de eventos colocando isso na parte superior do seu <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
fonte
Corrija-me se estiver errado, mas não é apenas para ouvintes de eventos anexados à janela?
Maciej Krawczyk
@MaciejKrawczyk sim, e aqueles que bolha
Jon z
18

As ferramentas de desenvolvedor do Firefox agora fazem isso. Os eventos são mostrados clicando no botão "ev" à direita da exibição de cada elemento, incluindo eventos jQuery e DOM .

Captura de tela do botão ouvinte de eventos das ferramentas de desenvolvedor do Firefox na guia Inspetor

simonzack
fonte
Eu observei um dos elementos dom na página em que o bookmarklet do Visual Event 2 está mostrando um evento conectado e vi o pequeno botão "ev" à direita, como você mostra.
Eric
O ouvinte de eventos do Firefox pode descobrir eventos delegados pelo jQuery, enquanto o Chrome precisa do plugin para fazer isso.
Nick Tsai
12

Se você possui o Firebug , pode console.dir(object or array)imprimir uma boa árvore no log do console de qualquer escalar, matriz ou objeto JavaScript.

Tentar:

console.dir(clickEvents);

ou

console.dir(window);
Michael Butler
fonte
9
Novo recurso no Firebug 1,12 getfirebug.com/wiki/index.php/GetEventListeners
Javier Constanzo
10

Solução totalmente funcional com base na resposta de Jan Turon - se comporta como getEventListeners()no console:

(Há um pequeno bug com duplicatas. De qualquer maneira, não quebra muito.)

(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]) - retornar lista de ouvintes de eventos, se o nome estiver definido, retornar matriz de ouvintes para esse evento

someElement.clearEventListeners([name]) - remova todos os ouvintes de eventos, se o nome estiver definido, remova apenas os ouvintes desse evento

n00b
fonte
3
Essa é uma boa resposta, mas não consigo fazê-la funcionar nos elementos bodye document.
David Bradshaw
Solução muito boa!
Dzhakhar Ukhaev
@ Peter Mortensen, por que você mudou o nome de Jan? :)
n00b
1
@ David Bradshaw: porque o corpo, documento ea janela têm o seu próprio protótipo e não o protótipo elemento ...
Stefan Steiger
1
@ n00b: Há um erro. É por isso que hmmm. Você usa o operador vírgula na instrução if para listener e useCaption ... O operador vírgula avalia cada um de seus operandos (da esquerda para a direita) e retorna o valor do último operando. Portanto, você remove o primeiro eventListener que corresponde ao useCapture, independentemente do tipo de evento ... Isso deve ser && em vez de vírgula.
23818 Stefan Steiger
8

O Opera 12 (que não é o mais recente mecanismo do Chrome Webkit) o Dragonfly tem isso há um tempo e é obviamente exibido na estrutura do DOM. Na minha opinião, é um depurador superior e é a única razão pela qual ainda uso a versão baseada no Opera 12 (não há versão v13, v14 e o v15 Webkit ainda não possui o Dragonfly)

insira a descrição da imagem aqui

Daniel Sokolowski
fonte
4

Maneira do protótipo 1.7.1

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
fonte
4

Recentemente, eu estava trabalhando com eventos e queria exibir / controlar todos os eventos em uma página. Tendo analisado as possíveis soluções, decidi seguir meu próprio caminho e criar um sistema personalizado para monitorar eventos. Então, eu fiz três coisas.

Primeiro, eu precisava de um contêiner para todos os ouvintes de eventos na página: esse é o EventListenersobjeto. Ele tem três métodos úteis: add(), remove()eget() .

Em seguida, eu criei um EventListenerobjeto para armazenar as informações necessárias para o evento, ou seja: target, type, callback, options, useCapture, wantsUntrusted, e acrescentou um métodoremove() para remover o ouvinte.

Por fim, estendi os métodos nativo addEventListener()e removeEventListener()para fazê-los funcionar com os objetos que criei ( EventListenere EventListeners).

Uso:

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

// bodyClickEvent.remove();

addEventListener()cria um EventListenerobjeto, adiciona EventListenerse retorna o EventListenerobjeto, para que possa ser removido mais tarde.

EventListeners.get()pode ser usado para visualizar os ouvintes na página. Ele aceita uma EventTargetou uma sequência (tipo de evento).

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

Demo

Digamos que queremos conhecer todos os ouvintes de eventos nesta página atual. Podemos fazer isso (supondo que você esteja usando uma extensão do gerenciador de scripts, Tampermonkey neste caso). O script a seguir faz isso:

// ==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);

E quando listamos todos os ouvintes, ele diz que existem 299 ouvintes de eventos. "Parece" que existem algumas duplicatas, mas não sei se são realmente duplicatas. Nem todo tipo de evento é duplicado; portanto, todos esses "duplicados" podem ser um ouvinte individual.

captura de tela do console listando todos os ouvintes de eventos nesta página

Código pode ser encontrado no meu repositório. Eu não queria publicá-lo aqui, porque é bastante longo.


Atualização: parece não funcionar com o jQuery. Quando examino o EventListener, vejo que o retorno de chamada é

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

Eu acredito que isso pertence ao jQuery e não é o retorno de chamada real. O jQuery armazena o retorno de chamada real nas propriedades do EventTarget:

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

insira a descrição da imagem aqui

Para remover um ouvinte de evento, o retorno de chamada real precisa ser passado para o removeEventListener()método Portanto, para fazer isso funcionar com o jQuery, ele precisa de mais modificações. Eu posso consertar isso no futuro.

akinuri
fonte
Funciona bem! Graças
mt025
3

Eu estou tentando fazer isso no jQuery 2.1, e com o $().click() -> $(element).data("events").click;método " " não funciona.

Percebi que apenas as funções $ ._ data () funcionam no meu 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
fonte
1

alterar essas funções permitirá que você registre os ouvintes adicionados:

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

leia o resto dos ouvintes com

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