关于 javascript:检查任何 DOM 元素的附加事件处理程序

Inspect attached event handlers for any DOM element

有没有办法查看 DOM 元素的任何事件附加了哪些函数/代码?使用 Firebug 或任何其他工具。


自 2011 年年中 Chrome 发布以来,Google Chrome 开发者工具中的元素面板就已具备此功能,并且自 2010 年以来 Chrome 开发者频道发布以来。

此外,为所选节点显示的事件侦听器按照它们在捕获和冒泡阶段被触发的顺序。

在 Mac OSX 上点击 command option i 在 Windows 上点击 Ctrl Shift i 在 Chrome

dev tools screenshot


使用传统 element.onclick= handler 或 HTML <element onclick="handler"> 附加的事件处理程序可以从脚本或调试器中的 element.onclick 属性中轻松检索。

使用 DOM 级别 2 事件 addEventListener 方法和 IE\\ 的 attachEvent 附加的事件处理程序目前根本无法从脚本中检索。 DOM Level 3 曾经提出 element.eventListenerList 来获取所有的监听器,但目前尚不清楚这是否会成为最终规范。今天在任何浏览器中都没有实现。

作为浏览器扩展的调试工具可以访问这些类型的侦听器,但我不知道实际上有什么。

一些 JS 框架留下了足够多的事件绑定记录来确定他们一直在做什么。 Visual Event 采用这种方法来发现通过一些流行框架注册的侦听器。


Chrome 开发工具最近发布了一些用于监控 JavaScript 事件的新工具。

TL;DR

Listen to events of a certain type using monitorEvents().

Use unmonitorEvents() to stop listening.

Get listeners of a DOM element using getEventListeners().

Use the Event Listeners Inspector panel to get information on event listeners.

查找自定义事件

根据我的需要,在 3rd 方代码中发现自定义 JS 事件,以下两个版本的 getEventListeners() 非常有用;

  • getEventListeners(window)
  • getEventListeners(document)

如果您知道事件侦听器附加到您的 DOM 节点,则将传递它而不是 windowdocument

已知事件

如果您知道要监控的事件,例如click 在文档正文上,您可以使用以下内容:monitorEvents(document.body, 'click');

您现在应该开始看到控制台中记录的 document.body 上的所有点击事件。


您可以使用 Allan Jardine 的 Visual Event 来检查页面上几个主要 JavaScript 库中所有当前附加的事件处理程序。它适用于 jQuery、YUI 和其他几个。

Visual Event 是一个 JavaScript 小书签,因此与所有主流浏览器兼容。


您可以通过查看 DOM 来查看直接附加的事件 (element.onclick = handler)。
您可以使用 FireBug 和 FireQuery 在 Firefox 中查看 jQuery 附加的事件。似乎没有任何方法可以使用 FireBug 查看 addEventListener 添加的事件。但是,您可以使用 Chrome 调试器在 Chrome 中查看它们。


您可以扩展您的 javascript 环境来跟踪事件侦听器。用一些代码package(或"重载")本机 addEventListener() 方法,这些代码可以记录从那时起添加的任何事件侦听器。您还必须扩展 HTMLElement.prototype.removeEventListener 以保留准确反映 DOM 中发生的情况的记录。

只是为了说明(未经测试的代码) - 这是一个示例,说明您将如何 \\'wrap\\' addEventListener 在对象本身上记录已注册的事件侦听器:

1
2
3
4
5
6
7
var nativeMethod = HTMLElement.prototype.addEventListener;
HTMLElement.prototype.addEventListener = function (type, listener) {
   var el = e.currentTarget;
   if(!(el.eventListeners instanceof Array)) { el.eventListeners = []}
   el.eventListeners.push({'type':type, 'listener':listener});
   nativeMethod.call(el, type, listener);
}

我很好奇@Rolf 的方法是否真的有效。请记住,这是将标准 HTMLElement.prototype.addEventLister() 替换为相同版本的"粗略"方式。显然,这只能是一种"用于测试的注入方法",并且对于任何接近"生产版本"的东西肯定都必须删除。

在测试时我发现,除了一个小故障(他的 e 没有在任何地方定义,但可以很容易地被 this 替换)之外,只要

,该方法确实有效

  • 您始终只在实际元素本身上使用 addEventListener()
  • 如果您不使用委托事件附件
  • 或通过设置 onclickoninput 等属性直接分配事件。

我继续研究是否可以使"嗅探"更加通用,并提出了以下修改版本:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
(nativeMethod=>{ // IIFE-closure to manipulate the standard addEventListener method:
  HTMLElement.prototype.addEventListener = function (type,fun) {
    (this.ELL=this.ELL||[]).push([type,fun]);
    nativeMethod.call(this,type,fun);
  }
})(HTMLElement.prototype.addEventListener);

// LIST direct and indirect event attachments for element `el`:
function listELfor(el){
  const events="click,change,input,keyup,keydown,blur,focus,mouseover,mouseout"
        .split(",").map(e=>"on"+e);  // possible direct event assignments to check up on
  const evlist = (el.ELL||[]).map(([t,f])=>[t,f.toString()]);
  events.forEach(e=> el[e] && (evlist[e]=[e.substr(2),el[e].toString()]) )
  let p=el.parentNode;
  if (p.tagName!=="HTML"){  // if available: run function on parent level recursively:
    evlist[p.tagName+(p.id?'#'+p.id:'')+(p.className?'.'+p.className:'')]=listELfor(el.parentNode);    
  }
  return evlist;
};

// ============ TESTING ==========================================
// now, let's do some sample event attachments in different ways:
const sp=document.querySelector('h1 span');  // sp = the target SPAN within H1
sp.addEventListener('click',function(e){console.log('first:',e.target)});
sp.addEventListener('click',function(e){console.log('second:',e.target.tagName)});
sp.addEventListener('click',function(e){console.log('third:',e.target.dataset.val)});
// attach an event to the parent node (H1):
sp.parentNode.addEventListener('click',function(e){console.log('Click event attached to H1, click-target is',e.target.tagName);});

// and finally, let's also assign an onclick event directly by using the ONCLICK attribute:
sp.onclick=e=>console.log('direct onclick on span, text:',e.target.textContent);

// Get all event handler functions linked to `sp`?
const allHandlers=listELfor(sp);
for (id in allHandlers) console.log(id,allHandlers[id]);
1
2
h1 span {cursor:pointer}
.as-console-wrapper {max-height:85% !important}
1
2
        Hello, <span data-val="123">THESE WORDS ARE CLICKABLE</span>
        <p>Some more text here to pad it out. This text should be unresponsive.</p>

IIFE 结构将 .addEventListener() 函数处理程序附件作为数组存储在相关 DOM 元素的 ELL 属性中。然后,函数 listELfor(el) 获取元素本身的这些函数处理程序,并向上遍历父层次结构以获取对其父项的分配。该函数还将处理使用 onclick 和类似属性的直接事件分配。

listELfor() 将返回一个带有额外属性的数组对象。这些属性不一定在普通的 console.log() 中可见。这就是我使用 for (id in allHandlers) 循环的原因。

请注意:
Chrome 也会列出这些"额外"的数组属性 - 甚至更多的属性,与父级及其父级的父级事件附件相关,如下所示:
enter image description here