【问题标题】:How to check whether dynamically attached event listener exists or not?如何检查动态附加的事件监听器是否存在?
【发布时间】:2012-07-12 10:05:34
【问题描述】:

这是我的问题:是否可以通过某种方式检查是否存在动态附加的事件侦听器?或者如何检查 DOM 中“onclick”(?)属性的状态?我已经像 Stack Overflow 一样在互联网上搜索解决方案,但没有运气。这是我的html:

<a id="link1" onclick="linkclick(event)"> link 1 </a>
<a id="link2"> link 2 </a> <!-- without inline onclick handler -->

然后在 Javascript 中,我将动态创建的事件侦听器附加到第二个链接:

document.getElementById('link2').addEventListener('click', linkclick, false);

代码运行良好,但我所有检测附加侦听器的尝试都失败了:

// test for #link2 - dynamically created eventlistener
alert(elem.onclick); // null
alert(elem.hasAttribute('onclick')); // false
alert(elem.click); // function click(){[native code]} // btw, what's this?

jsFiddle is here。 如果您单击“Add onclick for 2”,然后单击“[link 2]”,该事件会很好地触发, 但“测试链接 2”总是报告错误。 有人可以帮忙吗?

【问题讨论】:

  • 很抱歉,使用您当前的方法无法获取事件绑定:stackoverflow.com/questions/5296858/…
  • 您可以使用 chrome 开发工具:stackoverflow.com/a/41137585/863115
  • 您可以通过创建自己的存储来跟踪侦听器来做到这一点。请参阅my answer 了解更多信息。
  • 如果目标是防止已经添加的事件被再次添加,那么正确的answer is here。基本上,如果您使用命名函数而不是匿名函数,重复的相同事件侦听器将被丢弃,您无需担心它们。
  • 如果您担心有重复的侦听器,请在再次添加之前删除当前的事件侦听器。它并不完美,但很简单。

标签: javascript testing addeventlistener dom-events


【解决方案1】:

没有办法检查动态附加的事件监听器是否存在。

查看是否附加了事件侦听器的唯一方法是像这样附加事件侦听器:

elem.onclick = function () { console.log (1) }

然后,您可以通过返回 !!elem.onclick(或类似的东西)来测试事件侦听器是否附加到 onclick

【讨论】:

    【解决方案2】:

    我要做的是在你的函数之外创建一个布尔值,它以 FALSE 开始,并在你附加事件时设置为 TRUE。在您再次附加事件之前,这将作为您的某种标志。这是这个想法的一个例子。

    // initial load
    var attached = false;
    
    // this will only execute code once
    doSomething = function()
    {
     if (!attached)
     {
      attached = true;
      //code
     }
    } 
    
    //attach your function with change event
    window.onload = function()
    {
     var txtbox = document.getElementById('textboxID');
    
     if (window.addEventListener)
     {
      txtbox.addEventListener('change', doSomething, false);
     }
     else if(window.attachEvent)
     {
      txtbox.attachEvent('onchange', doSomething);
     }
    }
    

    【讨论】:

    • 这需要您更改附加事件的代码以跟踪它是否已附加(类似于thisthis answer)。不控制代码就不行。
    • 在我的用例中,用户可能会使用不同的选项卡打开相同的页面。这仅在事件仅附加在一个选项卡中的一个位置时才有效。布尔值无法真正与事件是否附加保持同步,因为我们只是猜测它不是在加载时
    【解决方案3】:

    这是我用来检查动态附加事件侦听器是否存在的脚本。我使用 jQuery 将事件处理程序附加到元素,然后触发该事件(在本例中为“单击”事件)。这样我就可以检索和捕获仅在附加了事件处理程序时才存在的事件属性。

    var eventHandlerType;
    
    $('#contentDiv').on('click', clickEventHandler).triggerHandler('click');
    
    function clickEventHandler(e) {
        eventHandlerType = e.type;
    }
    
    if (eventHandlerType === 'click') {
        console.log('EventHandler "click" has been applied');
    }
    

    【讨论】:

    • 您还可以为元素分配一个属性,您的元素基本上将其标记为附加了事件的元素;
    【解决方案4】:

    我只是通过尝试查看我的活动是否已附加才发现这一点......

    如果你这样做:

    item.onclick
    

    它将返回“null”

    但如果你这样做:

    item.hasOwnProperty('onclick')
    

    那么它就是“真”

    所以我认为当您使用“addEventListener”添加事件处理程序时,访问它的唯一方法是通过“hasOwnProperty”。我希望我知道为什么或如何,但可惜,经过研究,我还没有找到解释。

    【讨论】:

    • onclick.addEventListener 是分开的 - 它会影响属性,而 .addEventListener 不会
    【解决方案5】:

    我刚刚编写了一个脚本,可以让您实现这一目标。它为您提供了两个全局函数:hasEvent(Node elm, String event)getEvents(Node elm),您可以使用它们。请注意,它修改了EventTarget 原型方法add/RemoveEventListener,并且不适用于通过HTML 标记或elm.on_event = ... 的javascript 语法添加的事件

    More info at GitHub

    Live Demo

    脚本:

    var hasEvent,getEvents;!function(){function b(a,b,c){c?a.dataset.events+=","+b:a.dataset.events=a.dataset.events.replace(new RegExp(b),"")}function c(a,c){var d=EventTarget.prototype[a+"EventListener"];return function(a,e,f,g,h){this.dataset.events||(this.dataset.events="");var i=hasEvent(this,a);return c&&i||!c&&!i?(h&&h(),!1):(d.call(this,a,e,f),b(this,a,c),g&&g(),!0)}}hasEvent=function(a,b){var c=a.dataset.events;return c?new RegExp(b).test(c):!1},getEvents=function(a){return a.dataset.events.replace(/(^,+)|(,+$)/g,"").split(",").filter(function(a){return""!==a})},EventTarget.prototype.addEventListener=c("add",!0),EventTarget.prototype.removeEventListener=c("remove",!1)}();
    

    【讨论】:

      【解决方案6】:

      例如,您始终可以使用 Chrome 检查器手动检查您的 EventListener 是否存在。 在元素选项卡中,您有传统的“样式”子选项卡并靠近它另一个:“事件侦听器”。 这将为您提供所有 EventListener 及其链接元素的列表。

      【讨论】:

        【解决方案7】:

        我做了类似的事情:

        const element = document.getElementById('div');
        
        if (element.getAttribute('listener') !== 'true') {
             element.addEventListener('click', function (e) {
                 const elementClicked = e.target;
                 elementClicked.setAttribute('listener', 'true');
                 console.log('event has been attached');
            });
        }
        

        在附加监听器时为元素创建一个特殊属性,然后检查它是否存在。

        【讨论】:

        • 这是我过去使用的方法。我的建议是使用非常具体的语法结构。 IE 使用事件本身而不是“侦听器”。所以“数据事件点击”。这为您希望进行多个活动的活动提供了一些灵活性,并使事情更具可读性。
        • 加上@conrad10781 似乎是最可靠和最简单的方法。如果由于任何原因重新渲染元素并且事件侦听器断开连接,则该属性也将被重置。
        • 我可能会发疯(对此有 74 票赞成),但我认为 elementClicked.setAttribute('listener', 'true'); 应该添加到 addEventListender 函数之外 - 否则它仅在事件触发时添加,因此实际上并不允许您检查事件侦听器是否可靠地附加。
        • 在发布时这是最高票数,但(如上面的评论)逻辑有点不对劲。下面是一个更好的选择 (stackoverflow.com/a/69189193/1265200)
        【解决方案8】:

        tl;dr:不,您不能以任何本机支持的方式执行此操作。


        我知道实现这一点的唯一方法是创建一个自定义存储对象,您可以在其中记录添加的侦听器。大致如下:

        /* Create a storage object. */
        var CustomEventStorage = [];
        

        第 1 步:首先,您需要一个可以遍历存储对象并返回给定元素(或 false)的元素记录的函数。

        /* The function that finds a record in the storage by a given element. */
        function findRecordByElement (element) {
            /* Iterate over every entry in the storage object. */
            for (var index = 0, length = CustomEventStorage.length; index < length; index++) {
                /* Cache the record. */
                var record = CustomEventStorage[index];
        
                /* Check whether the given element exists. */
                if (element == record.element) {
                    /* Return the record. */
                    return record;
                }
            }
        
            /* Return false by default. */
            return false;
        }
        

        第 2 步:然后,您将需要一个函数,该函数可以添加事件侦听器,但也可以将侦听器插入到存储对象中。

        /* The function that adds an event listener, while storing it in the storage object. */
        function insertListener (element, event, listener, options) {
            /* Use the element given to retrieve the record. */
            var record = findRecordByElement(element);
        
            /* Check whether any record was found. */
            if (record) {
                /* Normalise the event of the listeners object, in case it doesn't exist. */
                record.listeners[event] = record.listeners[event] || [];
            }
            else {
                /* Create an object to insert into the storage object. */
                record = {
                    element: element,
                    listeners: {}
                };
        
                /* Create an array for event in the record. */
                record.listeners[event] = [];
        
                /* Insert the record in the storage. */
                CustomEventStorage.push(record);
            }
        
            /* Insert the listener to the event array. */
            record.listeners[event].push(listener);
        
            /* Add the event listener to the element. */
            element.addEventListener(event, listener, options);
        }
        

        第3步:根据您的问题的实际需求,您将需要以下函数来检查元素是否已添加指定事件的事件监听器。

        /* The function that checks whether an event listener is set for a given event. */
        function listenerExists (element, event, listener) {
            /* Use the element given to retrieve the record. */
            var record = findRecordByElement(element);
        
            /* Check whether a record was found & if an event array exists for the given event. */
            if (record && event in record.listeners) {
                /* Return whether the given listener exists. */
                return !!~record.listeners[event].indexOf(listener);
            }
        
            /* Return false by default. */
            return false;
        }
        

        第 4 步:最后,您需要一个可以从存储对象中删除侦听器的函数。

        /* The function that removes a listener from a given element & its storage record. */
        function removeListener (element, event, listener, options) {
            /* Use the element given to retrieve the record. */
            var record = findRecordByElement(element);
        
            /* Check whether any record was found and, if found, whether the event exists. */
            if (record && event in record.listeners) {
                /* Cache the index of the listener inside the event array. */
                var index = record.listeners[event].indexOf(listener);
        
                /* Check whether listener is not -1. */
                if (~index) {
                    /* Delete the listener from the event array. */
                    record.listeners[event].splice(index, 1);
                }
        
                /* Check whether the event array is empty or not. */
                if (!record.listeners[event].length) {
                    /* Delete the event array. */
                    delete record.listeners[event];
                }
            }
        
            /* Add the event listener to the element. */
            element.removeEventListener(event, listener, options);
        }
        

        片段:

        window.onload = function () {
          var
            /* Cache the test element. */
            element = document.getElementById("test"),
        
            /* Create an event listener. */
            listener = function (e) {
              console.log(e.type + "triggered!");
            };
        
          /* Insert the listener to the element. */
          insertListener(element, "mouseover", listener);
        
          /* Log whether the listener exists. */
          console.log(listenerExists(element, "mouseover", listener));
        
          /* Remove the listener from the element. */
          removeListener(element, "mouseover", listener);
        
          /* Log whether the listener exists. */
          console.log(listenerExists(element, "mouseover", listener));
        };
        <!-- Include the Custom Event Storage file -->
        <script src = "https://cdn.rawgit.com/angelpolitis/custom-event-storage/master/main.js"></script>
        
        <!-- A Test HTML element -->
        <div id = "test" style = "background:#000; height:50px; width: 50px"></div>

        虽然 OP 发布这个问题已经过去了 5 年多,但我相信将来偶然发现它的人会从这个答案中受益,所以请随时提出建议或改进。 ?

        【讨论】:

        • 感谢您提供此解决方案,它简单而坚固。虽说有一点,但还是有一点小错误。函数“removeListener”检查事件数组是否为空,但三元组不正确。它应该说“if (record.listeners[event].length!=-1)”。
        【解决方案9】:

        理论上,您可以搭载 addEventListener 和 removeEventListener 为“this”对象添加删除标志。丑的我没测试过……

        【讨论】:

          【解决方案10】:

          可能重复:Check if an element has event listener on it. No jQuery 请在那里找到我的答案。

          基本上这里是 Chromium (Chrome) 浏览器的窍门:

          getEventListeners(document.querySelector('your-element-selector'));
          

          【讨论】:

          【解决方案11】:

          似乎没有跨浏览器功能来搜索在给定元素下注册的事件。

          但是,可以在某些浏览器中使用其开发工具查看元素的回调函数。这在尝试确定网页的功能或调试代码时很有用。

          火狐

          首先,在开发者工具的Inspector 选项卡中查看元素。可以这样做:

          • 在页面上通过右键单击网页上要检查的项目并从菜单中选择“检查元素”。
          • 在控制台中通过使用一个函数来选择元素,例如document.querySelector,然后点击元素旁边的图标在Inspector 选项卡。

          如果元素注册了任何事件,您将在元素旁边看到一个包含单词 Event 的按钮。单击它将允许您查看已在该元素中注册的事件。单击事件旁边的箭头可以查看它的回调函数。

          首先,在开发者工具的元素标签中查看元素。可以这样做:

          • 在页面上通过右键单击网页上要检查的项目并从菜单中选择“检查”
          • 在控制台中通过使用一个函数来选择元素,例如document.querySelector,右键单击该元素,然后选择“在元素面板中显示”以在其中查看它检查器选项卡。

          在显示包含网页元素的树的窗口部分附近,应该有另一个带有标题为“事件侦听器”选项卡的部分。选择它以查看已注册到元素的事件。要查看给定事件的代码,请单击其右侧的链接。

          在 Chrome 中,也可以使用 getEventListeners 函数找到元素的事件。但是,根据我的测试,getEventListeners 函数在将多个元素传递给它时不会列出事件。如果您想查找页面上所有具有监听器的元素并查看这些监听器的回调函数,您可以在控制台中使用以下代码来执行此操作:

          var elems = document.querySelectorAll('*');
          
          for (var i=0; i <= elems.length; i++) {
              var listeners = getEventListeners(elems[i]);
          
              if (Object.keys(listeners).length < 1) {
                  continue;
              }
          
              console.log(elems[i]);
          
              for (var j in listeners) {
                  console.log('Event: '+j);
          
                  for (var k=0; k < listeners[j].length; k++) {
                      console.log(listeners[j][k].listener);
                  }
              }
          }
          

          如果您知道在给定浏览器或其他浏览器中执行此操作的方法,请编辑此答案。

          【讨论】:

            【解决方案12】:

            如果我理解得很好,您只能检查是否已检查听众,但不能检查具体是哪个听众。

            因此,一些临时代码将填补处理您的编码流程的空白。一种实用的方法是使用变量创建state。例如,附加一个监听器的检查器如下:

            var listenerPresent=false
            

            那么如果你设置了一个监听器,只需改变值:

            listenerPresent=true
            

            然后在 eventListener 的回调中,您可以在其中分配特定的功能,并以同样的方式,根据某些状态作为变量分配对功能的访问,例如:

            accessFirstFunctionality=false
            accessSecondFunctionality=true
            accessThirdFunctionality=true
            

            【讨论】:

              【解决方案13】:

              在添加之前删除事件:

              document.getElementById('link2').removeEventListener('click', linkclick, false);
              document.getElementById('link2').addEventListener('click', linkclick, false);
              

              【讨论】:

              • 这是一个很好的技巧,但我认为它只有在你有一个对当前“添加”事件侦听器之一的函数的引用时才有效。我认为这显然是标准 API 的一个缺点,如果我们可以添加任何事件侦听器,那么应​​该也可以检查到目前为止添加了哪些事件侦听器。这几乎就像事件监听器目前是“只写变量”,这似乎是一个模糊的概念。
              【解决方案14】:

              这种方法不存在似乎很奇怪。最后是时候添加它了吗?

              如果你愿意,你可以像下面这样:

              var _addEventListener = EventTarget.prototype.addEventListener;
              var _removeEventListener = EventTarget.prototype.removeEventListener;
              EventTarget.prototype.events = {};
              EventTarget.prototype.addEventListener = function(name, listener, etc) {
                var events = EventTarget.prototype.events;
                if (events[name] == null) {
                  events[name] = [];
                }
              
                if (events[name].indexOf(listener) == -1) {
                  events[name].push(listener);
                }
              
                _addEventListener(name, listener);
              };
              EventTarget.prototype.removeEventListener = function(name, listener) {
                var events = EventTarget.prototype.events;
              
                if (events[name] != null && events[name].indexOf(listener) != -1) {
                  events[name].splice(events[name].indexOf(listener), 1);
                }
              
                _removeEventListener(name, listener);
              };
              EventTarget.prototype.hasEventListener = function(name) {
                var events = EventTarget.prototype.events;
                if (events[name] == null) {
                  return false;
                }
              
                return events[name].length;
              };
              

              【讨论】:

                【解决方案15】:

                我通常将一个类附加到元素上,然后检查该类是否存在,如下所示:

                let element = document.getElementsById("someElement");
                
                if(!element.classList.contains('attached-listener'))
                   element.addEventListener("click", this.itemClicked);
                
                element.classList.add('attached-listener');
                

                【讨论】:

                  【解决方案16】:

                  这样的东西应该有助于文档:

                  var listeners = window.getEventListeners(document);
                  Object.keys(listeners).forEach(event => {
                      console.log(event, listeners[event]);
                  });
                  

                  或者使用选择器:

                  getAllEventListeners = function(el) {
                   var allListeners = {}, listeners;
                  
                   while(el) {
                    listeners = getEventListeners(el);
                  
                    for(event in listeners) {
                     allListeners[event] = allListeners[event] || [];
                     allListeners[event].push({listener: listeners[event], element: el});  
                    }
                  
                    el = el.parentNode;
                   }
                  
                   return allListeners;
                  }
                  

                  【讨论】:

                    【解决方案17】:

                    我编写了一个 Chrome 扩展程序,它需要确定页面上的哪些元素响应点击。我是这样做的:

                    (1) 在 manifest.json 中,将“run_at”属性设置为“document_start”。 (我们需要在页面开始运行之前注入一个脚本。)

                    (2) 在您的内容脚本中,添加一些代码以将脚本注入到页面中,该脚本将覆盖 EventTarget.prototype.addEventListener 以标记所有动态分配的点击侦听器的元素:

                    let flagClickHandledElements = function() {
                        let oldEventListener = EventTarget.prototype.addEventListener;
                        EventTarget.prototype.addEventListener = function(event_name, handler_func) {
                            if (event_name === 'click') {
                                if (this.setAttribute) {
                                    this.setAttribute('data-has_click_handler', true);
                                }
                            }
                            if (oldEventListener)
                                oldEventListener(event_name, handler_func);
                        }
                    }
                    
                    function injectScript(func) {
                        let codeString = '(' + func + ')();';
                        let script = document.createElement('script');
                        script.textContent = codeString;
                        (document.head||document.documentElement).appendChild(script);
                    }
                    
                    injectScript(flagClickHandledElements);
                    

                    (3) 将“webNavigation”添加到 manifest.json 中的“权限”列表中

                    (4) 在后台脚本中添加一些代码,以在页面加载完成时通知内容脚本:

                    function onPageDoneLoading(details)
                    {
                        chrome.tabs.sendMessage(details.tabId, {"action": "doneloading"});
                    }
                    
                    chrome.webNavigation.onCompleted.addListener(onPageDoneLoading);
                    

                    (5) 当页面完成加载时,让您的内容脚本向页面中注入另一个脚本,该脚本会扫描页面上的所有元素以查找旧式“onclick”处理程序:

                    let gatherOldStyleClickHandledElements = function() {
                        let all_elements = document.getElementsByTagName("*");
                        for (let i = 0; i < all_elements.length; i++) {
                            let el = all_elements[i];
                            if (el.setAttribute && el.onclick) {
                                el.setAttribute('data-has_click_handler', true);
                            }
                        }
                    }
                    
                    function onReceiveMessage(request) {
                        if (request.action === 'doneloading') {
                            injectScript(gatherOldStyleClickHandledElements);
                        } else {
                            console.log('Unrecognized message');
                        }
                    
                        return Promise.resolve("Dummy response to keep the console quiet");
                    }
                    

                    (6) 最后,您可以在内容脚本中测试一个元素,看看它是否有这样的点击处理程序:

                    if (el.getAttribute('data-has_click_handler'))
                        console.log('yep, this element has a click handler');
                    

                    【讨论】:

                    • 我很想看看这个 chrome 扩展!我想我们可能有相同的想法
                    • 唉,我是为工作而构建的,所以创意部分不是我的,但我试图分享上面的所有技术位。
                    【解决方案18】:
                    var listenerPaste = pasteAreaElm.hasOwnProperty('listenerPaste');
                    if (!listenerPaste) {
                        pasteAreaElm.addEventListener("paste", onPasteEvent, false);
                        pasteAreaElm.listenerPaste = true;
                    }
                    

                    【讨论】:

                      【解决方案19】:

                      // 取这个并在匿名函数之外分配属性

                      const element = document.getElementById('div');
                      if (element && !element.hasAttribute('listenerOnClick')) {
                          element.addEventListener('click', function () {
                              const elementClicked = this;
                              // fnDoAnything(this); // maybe call a function with the elementClicked...
                              console.log('event has been attached');
                          });
                      }
                      element.setAttribute('listenerOnClick', 'true');
                      

                      【讨论】:

                      【解决方案20】:

                      2022 年更新:

                      我在 TypeScript 中编写了实用方法来基于 this answer 附加和分离事件,但已更正。希望有人觉得它有帮助。

                      export const attachEvent = (
                        element: Element,
                        eventName: string,
                        callback: () => void
                      ) => {
                        if (element && eventName && element.getAttribute('listener') !== 'true') {
                          element.setAttribute('listener', 'true');
                          element.addEventListener(eventName, () => {
                            callback();
                          });
                        }
                      };
                      
                      export const detachEvent = (
                        element: Element,
                        eventName: string,
                        callback: () => void
                      ) => {
                        if (eventName && element) {
                          element.removeEventListener(eventName, callback);
                        }
                      };
                      
                      

                      像这样导入并在任何地方使用它

                        attachEvent(domElement, 'click', this.myfunction.bind(this));
                        detachEvent(domElement, 'click', this.myfunction);
                      

                      【讨论】:

                        猜你喜欢
                        • 1970-01-01
                        • 1970-01-01
                        • 2018-03-24
                        • 1970-01-01
                        • 2018-10-11
                        • 2011-06-01
                        • 2016-05-06
                        • 2012-10-10
                        • 1970-01-01
                        相关资源
                        最近更新 更多