Javascript/DOM:如何删除DOM对象的所有事件监听器?

143

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

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

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

编辑2:我找到了一种方法,它可以工作,但不可能用于我的情况:

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

你如何附加事件? - Felix Kling
2
标题询问对象的“元素”,而实际问题询问的是“事件”。您想要删除子元素还是事件? - Pär Wieslander
哦,该死,那是因为我在写那个的时候想着其他事情了...我关心的是事件。 - Florian Müller
1
我不确定确切的情况,但在某些情况下,作为解决办法,您可以使用“on *”方法(例如div.onclick = function),它始终与单个监听器一起工作,并且很容易通过 div.onclick=null 进行移除。当然,在这种情况下,您不应该使用 addEventListener,因为它会添加一个不同于 onclick 中的监听器。 - venimus
也许更优雅的解决方案是简单地扩展EventTarget,覆盖其addEventListener和removeEventListener方法以维护包含处理程序函数引用的数据结构。请参见Johns和angstyloop的解决方案,了解此方法的示例。在angsyloop的评论中链接的完整Gist中有测试,您可以在浏览器控制台中运行它们。在运行之前,请先阅读奇怪的JS。 - angstyloop
19个回答

157

我不确定你的意思是什么,是要删除某种类型的所有事件处理程序还是删除一种类型的所有事件处理程序?

删除所有事件处理程序

如果您想删除所有事件处理程序(任何类型的),可以使用clone节点并用其副本替换它:

var clone = element.cloneNode(true);

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


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

另一种方法是使用removeEventListener(),但我猜你已经尝试过了,但没有生效。 这里有个问题:

调用addEventListener并传入一个匿名函数,每次都会创建一个新的监听器。调用removeEventListener并传入一个匿名函数没有效果。每次调用匿名函数都会创建一个唯一的对象,它不是对现有对象的引用,但可能调用一个对象。以这种方式添加事件侦听器时,确保仅添加一次,并且在被添加到的对象被销毁之前,它是永久性的(无法删除)。

实际上,你将一个返回函数的函数传递给了addEventListener作为eventReturner

你有两种可能解决方法:

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

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

  2.  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')

演示

注意: 如果你的代码运行时间很长,而且你创建和删除了很多元素,你必须确保在销毁它们时删除_eventHandlers中包含的元素。


@Florian:当然,这段代码可以改进,但它应该能给你一个想法...祝你编程愉快! - Felix Kling
2
你尝试过在多个div元素中使用吗?节点变量实际上会被转换为一个字符串,例如'[object HTMLDivElement]',这意味着你最终会将所有内容添加到同一个节点中。 - cstruter
我猜这里有个打字错误,应该是 addListener 应该改为 addEvent。 - AGamePlayer
在删除监听器后,您忘记清空_eventHandlers: delete _eventHandlers[node]; - Simon Backx
5
var clone = element.cloneNode(true); element.replaceWith(clone); - asheroto
显示剩余7条评论

71

这将从子元素中移除所有监听器,但对于大型页面而言会比较慢。编写起来非常简单而直接。

element.outerHTML = element.outerHTML;

4
好的回答。对于叶节点来说,这似乎是最好的想法。 - user3055938
5
这段代码的意思是 将文档中 <body> 标签包含的内容替换为它自身所包含的内容 - Ryan
7
@Ryan,ن½ هڈ¯ن»¥ن½؟用document.body.outerHTMLن»£و›؟document.querySelector('body').outerHTMLم€‚è؟™و ·هپڑهڈ¯ن»¥ه¾—هˆ°ç›¸هگŒçڑ„结و‍œï¼Œو›´هٹ ç®€و´پم€‚ - Jay Dadhania
更简单的方法是:element.outerHTML += ""; 裁纸方式:变量引用指向此元素将继续指向原始的DOM节点。它被从文档中移除,然后插入新的节点来替换。因此,例如使用jQuery const link = $('#link'); link[0].outerHTML+=""; link.attr("href","/x.html"); 更改了jQuery集合中节点的href属性,但这不是页面中的那个节点!并且const在刷新集合时将会糟糕。 - Wil

27

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

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

2
不确定为什么这不是正确的答案。这个问题在SO上出现了很多次,许多答案都使用克隆方法来处理该问题。 - jimasun
65
似乎 getEventListeners 只在 WebKit 开发工具和 FireBug 中可用,不能直接在代码中调用。 - corwin.amber
12
似乎只能在Chrome控制台上使用。即使在您的网页脚本中也不起作用。 - Reuel Ribeiro
6
getEventListeners(document).click.forEach((e)=>{e.remove()})会产生这个错误:VM214:1 Uncaught TypeError: e.remove is not a function - Ryan
7
实际上只支持Chrome浏览器。由于这不是跨浏览器的,因此它是一个不好的解决方案。 - Michael Paccione
显示剩余3条评论

17

正如corwin.amber所说,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) }
  ) 
}

我在尝试禁用一家新闻网站烦人的版权保护时,偶然发现了这篇文章。

享受吧!


1
这是针对移除监听器的最佳解决方案,例如在不移除所有监听器的情况下从文档节点中移除特定的监听器。 - Trevor
1
这个运行良好,是最佳答案。@Jmakuc 非常感谢。 - Thavamani Kasi
14
火狐浏览器告诉我“getEventListeners”未定义? - rovyko
3
这是一个糟糕的解决方案,因为目前只有Chrome支持getEventListeners。 - Michael Paccione
@rovyko 请查看这个Firefox的解决方法:https://github.com/colxi/getEventListeners/issues/1 - baptx

9

克隆该元素并用其克隆体替换该元素。事件不会被克隆。

elem.replaceWith(elem.cloneNode(true));

使用 Node.cloneNode() 进行克隆DOM对象elem,它会忽略所有的事件处理程序(尽管如Jan Turoň's answer中所指出的,像onclick="…"这样的属性将保留)。接着使用Element.replaceWith()来用克隆体替换elem。对我而言,简单地将克隆体匿名赋值给自身并没有起作用。

相比重新定义elem.outerHTML本身 (如pabombs's answer所提议的),这种方法应该更快、更干净,但可能比那些迭代清除每个侦听器的答案要慢一些 (需要注意的是getEventListeners()似乎仅在Chrome的开发控制台中可用,在其他Chrome浏览器版本和Firefox中完全不可用)。大概当需要清除的侦听器数量较高时,这种非循环的解决方案会更快一些。

(这是Felix Kling's answer的一个简化版本,得益于来自asheroto's comment的帮助。)


6
你可以添加一个钩子函数来拦截所有对 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,以及其他各种事项。


我的方法与John类似。思路是子类化EventTarget,覆盖其addEventListener和removeEventListener方法,同时将处理程序函数保存在数据结构中,就像John所做的那样。在我评论中链接的完整Gist中,我还添加了一些单元测试。它总是可以使用更多的反馈,所以欢迎提出意见。 - angstyloop

5

您可以通过分配功能来删除所有其他点击。

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>


2
您确实可以像@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

我发现并且测试过的唯一简单有效的方法是:

假设我们想要添加两个事件监听器

const element = document.getElementById("element");
element.addEventListener('mouseover', 
  ()=>{ 
     // some task
  });

element.addEventListener('mouseout', 
  ()=>{ 
     // some task
  });

现在您可以通过简单地执行以下操作来删除这两个侦听器:
element.replaceWith(element.cloneNode(true));

相同的答案已经存在。 - Moritz Ringler

1
我知道这是一个老问题,但对我来说唯一有效的方法是:
``` parentOfTheElement.innerHTML = parentOfTheElement.innerHTML; ```
虽然其他解决方案实际上可以删除所有侦听器,但当使用outerHTML技巧或cloneNode()时,我添加新侦听器时遇到了问题。

outerHTML和cloneNode方法的问题在于你直接操作节点本身,当节点被替换后,你的变量仍然指向未替换的节点。你可以通过一个输入元素"<input id=foo value=bar>"来验证这一点,const f = foo; f.outerHTML+=""; f.value="baz"; console.log(foo.value, f.value) "bar baz",所以你需要在替换节点后重新获取节点。这个问题同样适用于集合,比如jQuery,const e = $('#foo'); e[0].outerHTML+=""; e.val('baz'); foo.value // "bar" - Wil

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