Как найти прослушиватели событий на узле DOM при отладке или из кода JavaScript?

842

У меня есть страница, где некоторые слушатели событий прикреплены к полям ввода и полям выбора. Есть ли способ узнать, какие слушатели событий наблюдают за конкретным узлом DOM и для какого события?

События прикрепляются с использованием:

  1. Прототип Event.observe ;
  2. DOM's addEventListener;
  3. Как атрибут элемента element.onclick.
Navneet
источник
3
Как события прикрепляются в первую очередь? Вы используете библиотеку (например, Prototype, jQuery и т. Д.)?
Crescent Fresh
Важно отметить, что несколько функций обратного вызова могут быть присоединены к одному и тому же типу события через element.addEventListener(type, callback, [bubble]), при этом они element.onclick = functionбудут перезаписываться при каждом назначении.
Брэден Бест

Ответы:

507

Если вам просто нужно проверить, что происходит на странице, вы можете попробовать букмарклет Visual Event .

Обновление : доступно визуальное событие 2

Эндрю Хеджес
источник
1
Отлично! Бьет EventBugплагин Firebug .
Робин Мабен
22
Это добавляет к странице миллионы элементов, многие из которых являются изображениями. Его полезность значительно снижается на странице со многими обработчиками событий (у меня 17k, а для загрузки Visual Event потребовалось около 3 минут).
Тони Р
15
Я считаю, что упомянутое здесь
kmote
1
Visial Event не работает, если страница ссылается на стороннюю библиотеку js. Возникает ошибка: XMLHttpRequest не может загрузить A.com/js/jquery-ui-1.10.3.custom.js?_=1384831682813 . Origin B.com не разрешен Access-Control-Allow-Origin.
Привет
5
Внутренние инструменты разработчика Chrome и FF (F12) имеют встроенную программу просмотра событий! Chrome: на вкладке «Элементы» выберите и элемент и увидите справа: Стиль, Вычисленные,
Прослушиватели
365

Это зависит от того, как прикреплены события. Для иллюстрации предположим, что у нас есть следующий обработчик кликов:

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

Мы собираемся прикрепить его к нашему элементу, используя разные методы, некоторые из которых позволяют проверять, а некоторые нет.

Метод А) один обработчик события

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

Метод Б) несколько обработчиков событий

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

Метод 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 (хранит обработчик внутри объекта)

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

(См. jQuery.fn.dataИ jQuery.data)

Метод D): прототип (грязный)

$(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 до 1.6.0.3 включительно (здесь очень сложно)

    // 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 (немного лучше)

    // inspect
    var clickEvents = element.getStorage().get('prototype_event_registry').get('click');
    clickEvents.each(function(wrapper){
        alert(wrapper.handler) // alerts "function() { alert('clicked!') }"
    })
    
Свежий полумесяц
источник
3
Спасибо за обновление этого. К сожалению, вы должны проходить через каждый тип обработчика.
Кит Бентруп
4
В разделе «Метод B» (addEventListener) приведен ответ о состоянии средств перечисления для обработчиков, зарегистрированных с помощью чистого API-интерфейса событий DOM: stackoverflow.com/questions/7810534/…
Николай
@John: спасибо за комментарий. У вас есть пример «реального JavaScript» для извлечения слушателей, ранее добавленных через addEventListener?
Crescent Fresh
6
@ Ян, это не работает для jQuery 1.7.2, есть ли другой подход для этой версии?
Томдемайт
14
@tomdemuyt В jQuery события теперь хранятся во внутреннем массиве данных, а не доступны через, .data('events')как это было раньше. Чтобы получить доступ к внутренним данным события, используйте $._data(elem, 'events'). Обратите внимание, что эта функция помечена «только для внутреннего использования» в исходном коде jQuery, поэтому нет никаких обещаний, что она всегда будет работать в будущем, но я полагаю, что она работает с jQuery 1.7 и работает до сих пор.
Мэтт Браун
351

Поддержка Chrome, Firefox, Vivaldi и Safari getEventListeners(domElement)в консоли инструментов разработчика.

Для большинства целей отладки это можно использовать.

Ниже очень хорошая ссылка для его использования: https://developers.google.com/web/tools/chrome-devtools/console/utilities#geteventlisteners

Raghav
источник
1
+1. Код для конкретного браузера не является для меня проблемой, потому что я пытаюсь заставить страницу, которую я не контролирую, работать должным образом.
Grault
9
приятно, но работает только в командной строке консоли Chrome :(
gillyspy
5
@Raghav ах спасибо, использование НЕ: как я сначала подумал :)getEventListeners(object) obj getEventListeners()
jave.web
11
Вы могли бы сэкономить мне 3 часа, если бы вы объяснили, как получить исходный код EventListener. Эта «ссылка» ничего не объясняла. В случае , если кто - то был на потери вот как: var list = getEventListeners(document.getElementById("YOURIDHERE")); console.log(list["focus"][0]["listener"].toString()). Измените «фокус» на событие, которое вы хотите проверить, и на номер, если на одном событии присутствуют несколько слушателей.
doABarrelRoll721
46
СОВЕТ: getEventListeners($0)получите прослушиватели событий для элемента, на котором вы сосредоточены в инструментах разработчика Chrome. Я использую это все время. Не люблю использовать функции
querySelector
91

Инспектор WebKit в браузерах Chrome или Safari теперь делает это. Он отобразит прослушиватели событий для элемента DOM, когда вы выберете его на панели элементов.

Ishan
источник
9
Я не уверен, что он показывает все обработчики событий; только один обработчик событий HTML.
Хайз
3
Я должен упомянуть плагин EventBug для Firebug для полноты < softwareishard.com/blog/category/eventbug >
Николай
Это только сделало мой день, у некоторого унаследованного кода-прототипа было несколько функций, привязанных к одному и тому же событию на одном и том же элементе, и я умирал, пытаясь отследить их. Большое спасибо, Ишан.
silicrockstar
70

Можно перечислить всех слушателей событий в JavaScript: это не так сложно; вам просто нужно взломать prototypeметод HTML элементов ( перед добавлением слушателей).

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

Теперь у каждого элемента привязки ( a) будет lastListenerInfoсвойство, содержащее всех его слушателей. И это даже работает для удаления слушателей с анонимными функциями.

Иван Кастелланос
источник
4
Этот метод не будет работать, если вы пишете сценарий пользователя или сценарий содержимого. Мало того, что в эти дни, скорее всего, появится песочница, но как вы можете гарантировать порядок исполнения?
Хайз
Этот метод работает с Chrome / 19 и FF / 12. порядок выполнения может быть гарантирован, если вы подключите его раньше других сценариев
Цуры Бар Йохай
8
Не могли бы вы просто изменить Node.prototypeвместо этого? Вот откуда и HTMLAnchorElementнаследует .addEventListener.
Esailija
3
Вы предполагаете, что пользовательский агент реализует наследование прототипов для объектов хоста DOM и позволяет вам изменять их. Ни одна из этих идей не является хорошей: не изменяйте объекты, которыми вы не владеете .
RobG
1
Это довольно бесполезно - оно показывает, какие EventListeners были добавлены, что может быть хорошо в некоторых случаях, но не то, что было удалено. Так что, если вы пытаетесь отладить то, что было удалено, а что нет, просто нет пути.
получил
52

Используйте getEventListeners в Google Chrome :

getEventListeners(document.getElementByID('btnlogin'));
getEventListeners($('#btnlogin'));
Пранай Сони
источник
1
Uncaught ReferenceError: getEventListeners не определен
Брайан Грейс
3
getEventListeners работает только в консоли Chrome devtools. И это дубликат этого более сложного ответа: stackoverflow.com/a/16544813/1026
Николай
41

(Переписать ответ на этот вопрос, поскольку он уместен здесь.)

При отладке, если вы просто хотите увидеть события, я рекомендую либо ...

  1. Визуальное событие
  2. Раздел « Элементы » инструментов разработчика Chrome: выберите элемент и найдите «Прослушиватели событий» в правом нижнем углу (аналогично в Firefox)

Если вы хотите использовать события в своем коде и используете jQuery до версии 1.8 , вы можете использовать:

$(selector).data("events")

чтобы получить события. Начиная с версии 1.8, использование .data («события») прекращено (см. Эту заявку об ошибке ). Ты можешь использовать:

$._data(element, "events")

Другой пример: записать все события кликов по определенной ссылке в консоли:

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

(см. http://jsfiddle.net/HmsQC/ для рабочего примера)

К сожалению, при использовании данных $ ._ это не рекомендуется, за исключением отладки, поскольку это внутренняя структура jQuery, и она может измениться в будущих выпусках. К сожалению, я не знаю других легких способов доступа к событиям.

Люк
источник
1
$._data(elem, "events")не работает с jQuery 1.10, по крайней мере, для событий, зарегистрированных с использованием $(elem).on('event', ...). Кто-нибудь знает, как их отладить?
Мариус Гедминас
4
Я не уверен, но я думаю, что первым параметром $._dataможет быть элемент, а не объект jQuery. Так что я должен исправить свой пример выше, чтобы бытьconsole.log($._data($myLink[0], "events").click);
Люк
29

1: Prototype.observeиспользует Element.addEventListener (см. Исходный код )

2: Вы можете переопределить, Element.addEventListenerчтобы запомнить добавленных слушателей (удобное свойство EventListenerListбыло удалено из предложения спецификации DOM3). Запустите этот код, прежде чем какое-либо событие будет прикреплено:

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

Читать все события по:

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

И не забудьте переопределить, Element.removeEventListenerчтобы удалить событие из пользовательского Element.eventListenerList.

3: Element.onclickсобственность нуждается в особом уходе здесь:

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

4: не забывайте Element.onclickатрибут содержимого: это две разные вещи:

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

Так что вам тоже нужно с этим справиться:

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

Букмарклет Visual Event (упомянутый в самом популярном ответе) крадет только кэш пользовательского обработчика библиотеки:

Оказывается, что не существует стандартного метода, предоставляемого рекомендуемым W3C интерфейсом DOM, чтобы выяснить, какие прослушиватели событий подключены к определенному элементу. Хотя это может показаться упущением, было предложение включить свойство eventListenerList в спецификацию DOM уровня 3, но, к сожалению, оно было удалено в более поздних версиях. В связи с этим мы вынуждены рассматривать отдельные библиотеки Javascript, которые обычно поддерживают кэш прикрепленных событий (чтобы впоследствии их можно было удалить и выполнить другие полезные абстракции).

Таким образом, чтобы визуальное событие отображало события, оно должно иметь возможность анализировать информацию о событиях из библиотеки Javascript.

Переопределение элементов может быть сомнительным (т. Е. Потому, что есть некоторые специфические функции DOM, такие как живые коллекции, которые не могут быть закодированы в JS), но он изначально поддерживает eventListenerList и работает в Chrome, Firefox и Opera (не работает в IE7 ).

Ян Турош
источник
23

Вы можете обернуть нативные методы DOM для управления слушателями событий, поместив это в верхнюю часть вашего <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
источник
Пожалуйста, поправьте меня, если я ошибаюсь, но не для слушателей событий, прикрепленных к окну?
Мацей Кравчик
@MaciejKrawczyk да, и те, которые пузыри
Jon z
18

Инструменты разработчика Firefox теперь делают это. События отображаются нажатием кнопки «ev» справа от каждого элемента, включая события jQuery и DOM .

Снимок экрана: кнопка прослушивания событий инструментов разработчика Firefox на вкладке инспектора

simonzack
источник
Я посмотрел на один из элементов dom на странице, на которой букмарклет Visual Event 2 отображает проводное событие, и я увидел маленькую кнопку «ev» справа, как вы видите.
Эрик
Слушатель событий Firefox может обнаружить делегированные события jQuery, в то время как Chrome нужен плагин для этого.
Ник Цай
12

Если у вас есть Firebug , вы можете использовать console.dir(object or array)для печати хорошего дерева в журнале консоли любого скаляра JavaScript, массива или объекта.

Пытаться:

console.dir(clickEvents);

или

console.dir(window);
Майкл Батлер
источник
9
Новая функция в Firebug 1.12 getfirebug.com/wiki/index.php/GetEventListeners
Хавьер Констанцо,
10

Полностью рабочее решение, основанное на ответе Яна Турона - ведет себя как getEventListeners()из консоли:

(Есть небольшая ошибка с дубликатами. В любом случае, она не сильно ломается.)

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

Применение:

someElement.getEventListeners([name]) - вернуть список слушателей события, если установлено имя, вернуть массив слушателей для этого события

someElement.clearEventListeners([name]) - удалить все прослушиватели событий, если установлено имя, удалить только прослушиватели для этого события

n00b
источник
3
Это хороший ответ, но я не могу заставить его работать на bodyи documentэлементах.
Дэвид Брэдшоу
Действительно хорошее решение!
Джахар Ухаев
@ Питер Мортенсен, почему ты сменил имя Яна? :)
n00b
1
@ Дэвид Брэдшоу: потому что тело, документ и окно имеют свой собственный прототип, а не прототип Element ...
Стефан Штайгер
1
@ n00b: есть ошибка. Вот почему хммм Вы используете запятую в операторе if для listener и useCaption ... Оператор запятой оценивает каждый из его операндов (слева направо) и возвращает значение последнего операнда. Таким образом, вы удаляете первый eventListener, который соответствует в useCapture, независимо от типа события ... Это должно быть && вместо запятой.
Стефан Штайгер
8

Opera 12 (не последняя версия на основе движка Chrome Webkit) Dragonfly некоторое время имела это и, очевидно, отображается в структуре DOM. На мой взгляд, это превосходный отладчик, и это единственная причина, по которой я до сих пор использую версию для Opera 12 (версии v13, v14 нет, а в версии 15 для Webkit отсутствует Dragonfly)

введите описание изображения здесь

Даниэль Соколовский
источник
4

Прототип 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];
}
Рональд
источник
4

Я недавно работал с событиями и хотел просмотреть / контролировать все события на странице. Посмотрев на возможные решения, я решил пойти своим путем и создать собственную систему мониторинга событий. Итак, я сделал три вещи.

Во-первых, мне был нужен контейнер для всех прослушивателей событий на странице: это EventListenersобъект. Она состоит из трех полезных методов: add(), remove(), и get().

Затем я создал EventListenerобъект для хранения информации , необходимой для этого события, а именно: target, type, callback, options, useCapture, wantsUntrusted, и добавил метод remove()для удаления слушателя.

Наконец, я расширил native addEventListener()и removeEventListener()методы, чтобы они работали с объектами, которые я создал ( EventListenerи EventListeners).

Применение:

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

// bodyClickEvent.remove();

addEventListener()создает EventListenerобъект, добавляет его EventListenersи возвращает EventListenerобъект, чтобы его можно было удалить позже.

EventListeners.get()может быть использован для просмотра слушателей на странице. Он принимает EventTargetили строку (тип события).

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

демонстрация

Допустим, мы хотим знать каждого слушателя событий на этой текущей странице. Мы можем сделать это (при условии, что вы используете расширение диспетчера сценариев, в данном случае Tampermonkey). Следующий скрипт делает это:

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

И когда мы перечисляем всех слушателей, там говорится, что есть 299 слушателей событий. «Кажется» есть несколько дубликатов, но я не знаю, действительно ли они дубликаты. Не каждый тип события дублируется, поэтому все эти «дубликаты» могут быть отдельными слушателями.

снимок экрана консоли со списком всех слушателей событий на этой странице

Код можно найти в моем хранилище. Я не хотел размещать это здесь, потому что это довольно долго.


Обновление: это не похоже на работу с jQuery. Когда я проверяю EventListener, я вижу, что обратный вызов

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

Я считаю, что это принадлежит JQuery, а не реальный обратный вызов. jQuery сохраняет фактический обратный вызов в свойствах EventTarget:

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

введите описание изображения здесь

Чтобы удалить прослушиватель событий, фактический обратный вызов должен быть передан removeEventListener()методу. Так что для того, чтобы заставить это работать с jQuery, он нуждается в дальнейшей модификации. Я мог бы исправить это в будущем.

akinuri
источник
Работает отлично! Спасибо
mt025
3

Я пытаюсь сделать это в jQuery 2.1, и с помощью $().click() -> $(element).data("events").click;метода " " это не работает.

Я понял, что в моем случае работают только функции $ ._ data ():

	$(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>

Джоэл Барба
источник
1

изменение этих функций позволит вам регистрировать добавленные слушатели:

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

читать остальным слушателям с

console.log(someElement.onclick);
console.log(someElement.getAttribute("onclick"));
Эйден Уотерман
источник