【问题标题】:Javascript/DOM: How to remove all event listeners of a DOM object?Javascript/DOM:如何删除 DOM 对象的所有事件侦听器?
【发布时间】:2011-05-22 03:22:18
【问题描述】:

只是问题:有没有办法完全删除对象的所有事件,例如一个div?

编辑:我正在为每个 div.addEventListener('click',eventReturner(),false); 添加一个事件。

function eventReturner() {
    return function() {
        dosomething();
    };
}

EDIT2:我找到了一种可行的方法,但无法用于我的情况:

var returnedFunction;
function addit() {
    var div = document.getElementById('div');
    returnedFunction = eventReturner();
    div.addEventListener('click',returnedFunction,false); //You HAVE to take here a var and not the direct call to eventReturner(), because the function address must be the same, and it would change, if the function was called again.
}
function removeit() {
    var div = document.getElementById('div');
    div.removeEventListener('click',returnedFunction,false);
}

【问题讨论】:

  • 如何附加事件?
  • 标题询问对象的元素,而实际问题询问事件。您要删除子元素还是事件?
  • 哦,天哪,那是因为我在写这篇文章时一直在考虑其他事情……我关心的是事件
  • 我不知道确切的情况,但在某些情况下,作为一种解决方法,您可以使用“on*”方法(如 div.onclick = 函数),它始终适用于单个侦听器并且是易于删除为div.onclick=null。当然,在这种情况下,您不应该完全使用addEventListener,因为它会添加一个与onclick 中的侦听器不同的单独侦听器。

标签: javascript events dom


【解决方案1】:

您确实可以通过克隆节点来删除所有事件处理程序,正如@FelixKling 在他的回答中所建议的那样,但是不要忘记这一点

属性事件处理程序不受克隆影响

有这样的元素

<div id="test" onclick="alert(42)">test</div>

克隆后仍会在单击时发出警报。要移除这类事件,一般需要使用removeAttribute方法

const removeAttEvents = el =>
    [...el.attributes].forEach(att =>
        att.name.startsWith("on") && el.removeAttribute(att.name)
    );

然后有了上面的test 元素,调用removeAttEvents(test) 就可以摆脱点击处理程序。

【讨论】:

    【解决方案2】:

    我不确定您对删除所有事件的意思。删除特定类型事件的所有处理程序还是删除一种类型的所有事件处理程序?

    删除所有事件处理程序

    如果您想删除所有事件处理程序(任何类型),您可以clone 元素并将其替换为它的克隆:

    var clone = element.cloneNode(true);
    

    注意:这将保留属性和子项,但不会保留对 DOM 属性的任何更改。


    删除特定类型的“匿名”事件处理程序

    另一种方法是使用removeEventListener(),但我猜你已经尝试过了,但没有成功。 Here is the catch:

    每次调用addEventListener 到匿名函数都会创建一个新的侦听器。将removeEventListener 调用到匿名函数没有效果。匿名函数每次被调用时都会创建一个唯一的对象,它不是对现有对象的引用,尽管它可能会调用一个对象。以这种方式添加事件侦听器时,请确保它只添加一次,它是永久性的(无法删除),直到它被添加到的对象被销毁。

    您实际上是将一个匿名函数传递给addEventListener,因为eventReturner 返回一个函数。

    你有两种可能解决这个问题:

    1. 不要使用返回函数的函数。直接使用函数:

       function handler() {
           dosomething();
       }
      
       div.addEventListener('click',handler,false);
      
    2. addEventListener 创建一个包装器,它存储对返回函数的引用并创建一些奇怪的removeAllEvents 函数:

       var _eventHandlers = {}; // somewhere global
      
       const addListener = (node, event, handler, capture = false) => {
         if (!(event in _eventHandlers)) {
           _eventHandlers[event] = []
         }
         // here we track the events and their nodes (note that we cannot
         // use node as Object keys, as they'd get coerced into a string
         _eventHandlers[event].push({ node: node, handler: handler, capture: capture })
         node.addEventListener(event, handler, capture)
       }
      
       const removeAllListeners = (targetNode, event) => {
         // remove listeners from the matching nodes
         _eventHandlers[event]
           .filter(({ node }) => node === targetNode)
           .forEach(({ node, handler, capture }) => node.removeEventListener(event, handler, capture))
      
         // update _eventHandlers global
         _eventHandlers[event] = _eventHandlers[event].filter(
           ({ node }) => node !== targetNode,
         )
       }
      

    然后你可以使用它:

        addListener(div, 'click', eventReturner(), false)
        // and later
        removeAllListeners(div, 'click')
    

    DEMO

    注意:如果您的代码运行了很长时间并且您正在创建和删除大量元素,则必须确保在销毁 _eventHandlers 中包含的元素时将其删除.

    【讨论】:

    • @Florian:当然,代码可以改进,但它应该给你一个想法......快乐编码!
    • 您是否尝试过使用多个 div 元素?节点 var 实际上将被转换为字符串,例如'[object HTMLDivElement]' 这意味着您最终将所有内容添加到同一个节点。
    • @cstruter:是的……这是我早期使用 JavaScript 的时候……我会在有更多时间时更正代码。谢谢你告诉我。
    • 我猜有错字,addListener应该是addEvent
    • 请注意,element.parentNode 可以为空。可能应该对上面的那段代码进行 if 检查。
    【解决方案3】:

    您可以添加功能并通过分配它们来删除所有其他点击

    btn1 = document.querySelector(".btn-1")
    btn1.addEventListener("click" , _=>{console.log("hello")})
    btn1.addEventListener("click" , _=>{console.log("How Are you ?")})
    
    btn2 = document.querySelector(".btn-2")
    btn2.onclick = _=>{console.log("Hello")}
    btn2.onclick = _=>{console.log("Bye")}
    <button class="btn-1">Hello to Me</button>
    <button class="btn-2">Hello to Bye</button>

    【讨论】:

      【解决方案4】:

      例如,您可以添加一个清除事件侦听器的辅助函数

      function clearEventListener(element) {
       const clonedElement = element.cloneNode(true);
      element.replaceWith(clonedElement);
      return clonedElement;
      }
      

      只需将元素传递给函数,就是这样......

      【讨论】:

        【解决方案5】:

        你可以添加一个钩子函数来拦截所有对addEventHandler的调用。该钩子会将处理程序推送到可用于清理的列表中。例如,

        if (EventTarget.prototype.original_addEventListener == null) {
            EventTarget.prototype.original_addEventListener = EventTarget.prototype.addEventListener;
        
            function addEventListener_hook(typ, fn, opt) {
                console.log('--- add event listener',this.nodeName,typ);
                this.all_handlers = this.all_handlers || [];
                this.all_handlers.push({typ,fn,opt});
                this.original_addEventListener(typ, fn, opt);  
            }
        
            EventTarget.prototype.addEventListener = addEventListener_hook;
        }
        

        您应该将此代码插入主网页顶部附近(例如index.html)。在清理过程中,您可以遍历 all_handlers,并为每个调用 removeEventHandler。不用担心使用同一个函数多次调用 removeEventHandler。它是无害的。

        例如,

        function cleanup(elem) {
            for (let t in elem) if (t.startsWith('on') && elem[t] != null) {
                elem[t] = null;
                console.log('cleanup removed listener from '+elem.nodeName,t);
            } 
            for (let t of elem.all_handlers || []) {
                elem.removeEventListener(t.typ, t.fn, t.opt);
                console.log('cleanup removed listener from '+elem.nodeName,t.typ);
            } 
        }
        

        注意: IE 使用 Element 而不是 EventTarget,并将 => 更改为 function,以及其他各种东西。

        【讨论】:

          【解决方案6】:

          angular 有一个针对这个问题的 polyfill,你可以检查一下。我不太了解,但也许它可以提供帮助。

          const REMOVE_ALL_LISTENERS_EVENT_LISTENER = 'removeAllListeners';

              proto[REMOVE_ALL_LISTENERS_EVENT_LISTENER] = function () {
                  const target = this || _global;
                  const eventName = arguments[0];
                  if (!eventName) {
                      const keys = Object.keys(target);
                      for (let i = 0; i < keys.length; i++) {
                          const prop = keys[i];
                          const match = EVENT_NAME_SYMBOL_REGX.exec(prop);
                          let evtName = match && match[1];
                          // in nodejs EventEmitter, removeListener event is
                          // used for monitoring the removeListener call,
                          // so just keep removeListener eventListener until
                          // all other eventListeners are removed
                          if (evtName && evtName !== 'removeListener') {
                              this[REMOVE_ALL_LISTENERS_EVENT_LISTENER].call(this, evtName);
                          }
                      }
                      // remove removeListener listener finally
                      this[REMOVE_ALL_LISTENERS_EVENT_LISTENER].call(this, 'removeListener');
                  }
                  else {
                      const symbolEventNames = zoneSymbolEventNames$1[eventName];
                      if (symbolEventNames) {
                          const symbolEventName = symbolEventNames[FALSE_STR];
                          const symbolCaptureEventName = symbolEventNames[TRUE_STR];
                          const tasks = target[symbolEventName];
                          const captureTasks = target[symbolCaptureEventName];
                          if (tasks) {
                              const removeTasks = tasks.slice();
                              for (let i = 0; i < removeTasks.length; i++) {
                                  const task = removeTasks[i];
                                  let delegate = task.originalDelegate ? task.originalDelegate : task.callback;
                                  this[REMOVE_EVENT_LISTENER].call(this, eventName, delegate, task.options);
                              }
                          }
                          if (captureTasks) {
                              const removeTasks = captureTasks.slice();
                              for (let i = 0; i < removeTasks.length; i++) {
                                  const task = removeTasks[i];
                                  let delegate = task.originalDelegate ? task.originalDelegate : task.callback;
                                  this[REMOVE_EVENT_LISTENER].call(this, eventName, delegate, task.options);
                              }
                          }
                      }
                  }
                  if (returnTarget) {
                      return this;
                  }
              };
          

          ....

          【讨论】:

          • EVENT_NAME_SYMBOL_REGX 应该来自哪里?
          【解决方案7】:

          一种方法是添加一个调用 e.stopImmediatePropagation() 的新事件侦听器。

          【讨论】:

            【解决方案8】:

            为了完成答案,以下是在您访问网站并且无法控制生成的 HTML 和 JavaScript 代码时删除事件的真实示例。

            一些烦人的网站阻止您在登录表单上复制粘贴用户名,如果使用onpaste="return false" HTML 属性添加onpaste 事件,则很容易绕过。 在这种情况下,我们只需右键单击输入字段,在 Firefox 等浏览器中选择“检查元素”并删除 HTML 属性。

            但是,如果事件是这样通过 JavaScript 添加的:

            document.getElementById("lyca_login_mobile_no").onpaste = function(){return false};
            

            我们还必须通过 JavaScript 删除事件:

            document.getElementById("lyca_login_mobile_no").onpaste = null;
            

            在我的示例中,我使用了 ID“lyca_login_mobile_no”,因为它是我正在访问的网站使用的文本输入 ID。

            另一种删除事件(也将删除所有事件)的方法是删除节点并创建一个新节点,就像我们必须使用 addEventListener 使用我们无法使用的匿名函数添加事件一样使用removeEventListener 删除。 这也可以通过浏览器控制台通过检查元素、复制 HTML 代码、删除 HTML 代码然后将 HTML 代码粘贴到同一位置来完成。

            它还可以通过 JavaScript 更快和自动化地完成:

            var oldNode = document.getElementById("lyca_login_mobile_no");
            var newNode = oldNode.cloneNode(true);
            oldNode.parentNode.insertBefore(newNode, oldNode);
            oldNode.parentNode.removeChild(oldNode);
            

            更新:如果 Web 应用程序是使用 Angular 之类的 JavaScript 框架制作的,那么之前的解决方案似乎无法正常工作或破坏了应用程序。 允许粘贴的另一种解决方法是通过 JavaScript 设置值:

            document.getElementById("lyca_login_mobile_no").value = "username";
            

            目前,我不知道是否有一种方法可以在不破坏像 Angular 这样完全用 JavaScript 编写的应用程序的情况下删除所有表单验证和限制事件。

            【讨论】:

              【解决方案9】:

              仅限 Chrome

              当我试图在量角器测试中删除 EventListener 并且无权访问 测试中的侦听器,以下用于删除单一类型的所有事件侦听器:

              function(eventType){
                  getEventListeners(window).[eventType].forEach(
                      function(e){
                          window.removeEventListener(eventType, e.listener)
                      }
                  );
              }
              

              我希望这可以帮助某人,因为以前的答案是使用“删除”方法,从那时起它就不再起作用了。

              【讨论】:

                【解决方案10】:

                删除document上的所有事件

                一个班轮:

                for (key in getEventListeners(document)) { getEventListeners(document)[key].forEach(function(c) { c.remove() }) }
                

                漂亮的版本:

                for (key in getEventListeners(document)) {
                  getEventListeners(document)[key].forEach(function(c) {
                    c.remove()
                  })   
                }
                

                【讨论】:

                  【解决方案11】:

                  正如 corwin.amber 所说,Webkit 与其他 Webkit 之间存在差异。

                  在 Chrome 中:

                  getEventListeners(document);
                  

                  这为您提供了一个包含所有现有事件侦听器的对象:

                  Object 
                   click: Array[1]
                   closePopups: Array[1]
                   keyup: Array[1]
                   mouseout: Array[1]
                   mouseover: Array[1]
                   ...
                  

                  从这里你可以到达你想要移除的监听器:

                  getEventListeners(document).copy[0].remove();
                  

                  所以所有的事件监听器:

                  for(var eventType in getEventListeners(document)) {
                     getEventListeners(document)[eventType].forEach(
                        function(o) { o.remove(); }
                     ) 
                  }
                  

                  在 Firefox 中

                  有点不同,因为它使用了一个不包含删除函数的侦听器包装器。您必须获取要删除的侦听器:

                  document.removeEventListener("copy", getEventListeners(document).copy[0].listener)
                  

                  所有事件监听器:

                  for(var eventType in getEventListeners(document)) {
                    getEventListeners(document)[eventType].forEach(
                      function(o) { document.removeEventListener(eventType, o.listener) }
                    ) 
                  }
                  

                  我偶然发现这篇文章试图禁用新闻网站令人讨厌的复制保护。

                  享受吧!

                  【讨论】:

                  • 这是针对移除侦听器的最佳答案,例如在移除文档节点上的特定侦听器而不移除其所有侦听器的情况下。
                  • 这个工作正常,最好的答案。 @Jmakuc 非常感谢。
                  • Firefox 告诉我 getEventListeners 没有定义?
                  • 这是一个糟糕的解决方案,因为目前只有 Chrome 支持 getEventListeners。
                  【解决方案12】:

                  这将从子级中删除所有侦听器,但对于大页面来说会很慢。写起来非常简单。

                  element.outerHTML = element.outerHTML;
                  

                  【讨论】:

                  • 很好的答案。对于叶节点,这似乎是最好的主意。
                  • document.querySelector('body').outerHTML = document.querySelector('body').outerHTML 为我工作。
                  • @Ryan 而不是document.querySelector('body').outerHTML 你可以输入document.body.outerHTML
                  • 不知道是不是最好的方法,但绝对是最简单的?
                  【解决方案13】:

                  使用事件监听器自己的函数remove()。例如:

                  getEventListeners().click.forEach((e)=>{e.remove()})
                  

                  【讨论】:

                  • 不知道为什么这不是正确的答案。这个问题在 SO 上出现了很多次,可能答案使用克隆方法处理问题。
                  • 看来getEventListeners 仅在WebKit 开发工具和FireBug 中可用。不是你可以在你的代码中调用的东西。
                  • 无法让它工作,你将一个对象作为参数传递给getEventListeners()。例如:getEventListeners(document)getEventListeners(document.querySelector('.someclass'))
                  • 这似乎只适用于 chrome console。即使在页面上的脚本上也不起作用。
                  • getEventListeners(document).click.forEach((e)=&gt;{e.remove()}) 产生此错误:VM214:1 Uncaught TypeError: e.remove is not a function
                  【解决方案14】:

                  如果您执行以下操作,浏览器可能会为您执行此操作:

                  复制div及其属性,插入到旧的之前,然后把旧的内容移到新的,删除旧的?

                  【讨论】:

                  • 现在这实际上是一个好主意,但完全没有性能,如果你必须为 1'000 和 1'000'000 之间的大量 div 执行此操作......是的,这是一个大项目;)
                  • 一页中有 1M div?在此之前,您会遇到其他麻烦;)然后可能会使用事件委托...
                  【解决方案15】:
                  var div = getElementsByTagName('div')[0]; /* first div found; you can use getElementById for more specific element */
                  div.onclick = null; // OR:
                  div.onclick = function(){};
                  

                  //编辑

                  我不知道您使用什么方法附加事件。对于addEventListener,你可以使用这个:

                  div.removeEventListener('click',functionName,false); // functionName is the name of your callback function
                  

                  more details

                  【讨论】:

                  • 如果我将其设置为div.onClick = something,那么它会设置另一个事件 onClick,但前一个事件会保留,所以我有上一个事件,例如 null...
                  • 如果通过addEventListener()添加处理程序将不起作用。
                  • @Florian:不,那不是真的。如果使用这种添加事件处理程序的方法,则只能有 一个 事件处理程序。但如果你使用addEventListener()...,它确实会有所不同
                  • 但我见过这样的,所以如果我再次添加以前的函数,它将被调用两次......而且,就像 Felix King 所说,这不适用于 addEventListener......
                  猜你喜欢
                  • 1970-01-01
                  • 1970-01-01
                  • 2010-11-29
                  • 1970-01-01
                  • 1970-01-01
                  • 1970-01-01
                  • 1970-01-01
                  • 2018-06-29
                  • 2013-12-09
                  相关资源
                  最近更新 更多