在JavaScript中监听所有事件

84

我正在尝试找出如何监听JavaScript对象上的所有事件。

我知道我可以使用类似以下内容添加单个事件

element.addEventListener("click", myFunction);
element.addEventListener("mouseover", myFunction);
...

我正在尝试找出是否有全能方法,我想做类似以下的事情:

// Begin pseudocode
var myObj = document.getElementById('someID');

myObj.addEventListener(/*catch all*/, myFunction);

function myFunction() {
  alert(/*event name*/);
}
// End pseudocode

4
为什么你想要那样做? - putvande
4
你最终是否解决了这个问题?它是否可行? - hipkiss
9
@putvande 为什么不呢?并且,实际上不可能通过一个函数调用开始监听 ALL 事件。您需要知道它们的名称。请参阅下面的答案。我认为 user3780616 正试图找到在他执行特定操作时触发的事件。他正在尝试查找事件的名称。 - Bitterblue
@putvande,也许你正在制作一个自定义输入Web组件,并希望暴露所有正常事件,而不会通过为每个事件类型创建事件侦听器来大大减慢浏览器的速度,例如,这是一个巨大的问题。 - run_the_race
7个回答

95
一个更现代化的重写,来自@roman-bekkiev的回答:
Object.keys(window).forEach(key => {
    if (/^on/.test(key)) {
        window.addEventListener(key.slice(2), event => {
            console.log(event);
        });
    }
});

以下是一个进一步自定义事件过滤的示例:

/^on(key|mouse)/.test(key)


2
请注意,由于箭头函数('=>'),此代码在IE上无法正常工作。如果要在IE上使用eventListener,请使用Roman的答案。 - user2018
@ryanpcmcquen 我的意思是,你应该在子HTML节点上使用多个Object.getPrototypeOf()来获取事件属性。 - Jehong Ahn
太棒了!正是我要找的。如果您想删除垃圾邮件事件,请使用 /^on(?!mouse|pointer)/。 - Taurus
Object.keys不列出大多数事件侦听器。 - golopot
这是一个更全面的事件列表,可能会导致垃圾邮件!animationiteration, animationstart, click, dblclick, mouseout, mouseevent, mousemove, mouseover, mousein, pointerout, pointerup, pointermove, pointerrawupdate, pointerover, pointerdown, blur, mousedown, mouseup, transitionrun, transitionstart, transitioncancel, transitionend, scroll, scrollend - BoKKeR

52

获取标准元素的事件。

var myObj = document.getElementById('someID')
for (var key in myObj) {
    if (key.search('on') === 0) {
       myObj.addEventListener(key.slice(2), myFunction)
    }
}

但正如@jeremywoertink所提到的,任何其他事件也是可能的...


窗口上的所有事件怎么样? - user5047085
5
@Olegzandr,窗口有什么问题吗?只需要用“window”替换“myObj”。然而,请注意,这样您也将会监听一些不存在的事件,例如如果存在全局变量“onetwothree”,上面的代码将添加一个侦听器到事件“'etwothree'”。( ͡° ͜ʖ ͡°) - Roman Bekkiev

11

我讨厌这个问题没有本地或优雅的解决方案。

更好的解决方案?

使用target.addEventListener('*', ...),您可以订阅任何EventTarget的单个CustomEvent

    clear();

    /**
     * @param : source := EventTarget
     *  *   EventTarget.prototype
     *  *   Node (Element, Attr, etc)
     * @usage : [Node].addEventListener('*', ({ detail: e }) => {...}, false);
     */
    function proxyEventTargetSource(source) {
        var emit = source.dispatchEvent;  // obtain reference

        function proxy(event) {
            var { type } = event, any = new CustomEvent('*', { detail: event });  // use original event as detail
            if (!{ '*': true }[ type ]) emit.call(this, any);  // only emit "any" if type is not any.type ('*')
            return emit.call(this, event);
        }

        if ({ 'dispatchEvent': true }[ emit.name ]) source.dispatchEvent = proxy;  // attempt overwrite only if not already set (avoid rewrapping)
        return (source.dispatchEvent === proxy);  // indicate if its set after we try to
    }

    // proxyEventTargetSource(EventTarget.prototype);  // all targets
    proxyEventTargetSource(document);  // single target
    var e = new CustomEvent('any!', { detail: true });
    document.addEventListener('*', (e) => console.log('type: %s, original: %s, e: %O', e.type, e.detail.type, e), false);
    document.dispatchEvent(e);

当然,一种更本地或[也许]更优雅的方法是在目标的dispatchEvent方法上使用本地的Proxy,但这可能会为了本篇文章的目的而传达得更少。

要点: https://gist.github.com/cScarlson/875a9fca7ab7084bb608fb66adff0463

已知问题

显然,这仅在通过EventTargetsdispatchEvent方法驱动事件分派时有效。也就是说,通过鼠标事件(例如)自然触发事件不起作用。需要有一种方法来包装被自然事件触发调用的内部方法。

也就是说,如果您已经找到了解决此问题的方法,请在另一个答案中展示。


5
据我所知,这是可能的。
对于所有“本机事件”,我们可以通过迭代target.onevent属性来检索支持的事件列表,并为所有事件安装我们的监听器。
for (const key in target) {
    if(/^on/.test(key)) {
        const eventType = key.substr(2);
        target.addEventListener(eventType, listener);
    }
}

我所知道的唯一可以触发事件的方法是通过 EventTarget.dispatchEvent, 它被每个 Node(节点)以及因此而继承的 Element(元素)所使用。 为了监听所有这些手动触发的事件,我们可以全局代理 dispatchEvent 方法,并即时安装在我们刚刚看到的那个事件名上的监听器 ✨ ^^
const dispatchEvent_original = EventTarget.prototype.dispatchEvent;
EventTarget.prototype.dispatchEvent = function (event) {
    if (!alreadyListenedEventTypes.has(event.type)) {
        target.addEventListener(event.type, listener, ...otherArguments);
        alreadyListenedEventTypes.add(event.type);
    }
    dispatchEvent_original.apply(this, arguments);
};

函数片段

function addEventListenerAll(target, listener, ...otherArguments) {

    // install listeners for all natively triggered events
    for (const key in target) {
        if (/^on/.test(key)) {
            const eventType = key.substr(2);
            target.addEventListener(eventType, listener, ...otherArguments);
        }
    }

    // dynamically install listeners for all manually triggered events, just-in-time before they're dispatched ;D
    const dispatchEvent_original = EventTarget.prototype.dispatchEvent;
    function dispatchEvent(event) {
        target.addEventListener(event.type, listener, ...otherArguments);  // multiple identical listeners are automatically discarded
        dispatchEvent_original.apply(this, arguments);
    }
    EventTarget.prototype.dispatchEvent = dispatchEvent;
    if (EventTarget.prototype.dispatchEvent !== dispatchEvent) throw new Error(`Browser is smarter than you think!`);

}


// usage example
addEventListenerAll(window, (evt) => {
    console.log(evt.type);
});
document.body.click();
document.body.dispatchEvent(new Event('omg!', { bubbles: true }));


// usage example with `useCapture`
// (also receives `bubbles: false` events, but in reverse order)
addEventListenerAll(
    window,
    (evt) => { console.log(evt.type); },
    true
);
document.body.dispatchEvent(new Event('omfggg!', { bubbles: false }));

2
你可以使用支持通配符的EventEmitter2。采用你所说的全捕获的方法会存在问题,因为有太多事件类型,并且你也可以自己创建事件类型。你需要制定一个特定的事件类型数组,对其进行迭代并逐个绑定。

1

你应该选择想要监听的事件,将它们放入数组中并对每个事件进行迭代:

['click','mouseover'].forEach(function(ev) {
    el.addEventListener(ev, function() {
        console.log('event:', ev)
    })
})

现在似乎出了问题。我正在尝试追踪是谁在什么时候更改了什么,但目前还没有成功。 - BrendanJefferis
1
是的,Event.prototype已经更改过了。无论如何,它都是个问题,我将其从答案中删除。 - Mosho
2
那么一个好的解决方案应该能够获取所有事件名称的列表,有没有一种方法可以检索所有可能的事件名称列表? - user5047085
3
这并没有回答这个问题。 - Evgeny

-2
//listening for all click events on the document
   document.addEventListener('click', function (event) {

    //filtering for only events that happen on elements that contain the class
    //view_btn.          
  if (event.target.classList.contains( 'view_btn' )){
//logging out the id of the element        
          var id_of_clicked_element = event.target.getAttribute("id"); //
          console.log("button clicked has is of " + id_of_clicked_element)

        }
    });

4
如何监听文档上的所有事件?您只需添加一个“click”侦听器即可。 - Raanan W
笔误,我想说的是所有点击事件。 - Mario Gomez

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接