JavaScript removeEventListener 不起作用

128

我有以下代码来添加事件监听器

 area.addEventListener('click',function(event) {
              app.addSpot(event.clientX,event.clientY);
              app.addFlag = 1;
          },true);

它按预期正常工作。稍后在另一个函数中,我尝试使用以下代码删除事件侦听器:

 area.removeEventListener('click',function(event) {
              app.addSpot(event.clientX,event.clientY);
              app.addFlag = 1;
          },true);

但是事件监听器并没有被移除...为什么会发生这种情况?我的removeEventListener()有问题吗? 注意:这里的area类似于document.getElementById('myId')


可能是JavaScript:删除事件侦听器的重复问题。 - Heretic Monkey
15个回答

195

这是因为两个匿名函数是完全不同的函数,你的 removeEventListener 参数并不是之前附加的函数对象的引用。

function foo(event) {
              app.addSpot(event.clientX,event.clientY);
              app.addFlag = 1;
          }
 area.addEventListener('click',foo,true);
 area.removeEventListener('click',foo,true);

74
+1 确实。使用函数bindAPI绑定this后,函数的签名会发生变化。因此,在绑定this之后,总是将函数分配给一个变量,以便在removeListener中使用相同的变量。在TypeScript中,这个问题会更加明显。 - NiRUS
4
不能传递函数参数,例如foo(1) - IC_
37
如果有人在使用类时尝试像这样使用 this.onClick = this.onClick.bind(this),然后在任何监听器之前添加 btn.addEventListener('click', this.onClick),最后使用 btn.removeEventListener('click', this.onClick) - joseluisq
@Herrgott 为了将参数传递给处理程序函数,您可以使用柯里化:foo = (argumentToPass) => (event) => { doSomething(); },然后 xyz.addEventListener('click', foo('myarg'), true);foo('myarg') 将返回另一个函数,其中 argumentToPass 设置为 myarg。只需记住在实际代码中保留对 fn 的引用即可 :-) - raven-king
请注意,截至2020年底,我们不需要使用removeEventListener来删除事件侦听器,现在我们可以使用中止信号,这意味着我们根本不需要构造对原始函数的引用,而是可以使用AbortController。详情请参见:https://dev59.com/iWkv5IYBdhLWcg3wqSf1#70498530 - Mike 'Pomax' Kamermans
显示剩余2条评论

28
我发现对于Windows对象,最后一个参数 "true" 是必需的。如果没有捕获标志,则删除操作无法完成。

26
在React函数组件中,确保使用useCallback(() => {})钩子来定义回调函数。如果你没有这样做,每次重新渲染时回调函数都会是不同的,导致removeEventListener方法无法正常工作。
const scrollCallback = useCallback(() => { // do sth. }, []);
window.addEventListener("scroll", scrollCallback, true);
window.removeEventListener("scroll", scrollCallback, true);

这救了我的命,我以为它是const,所以在重新渲染时不会被重新声明,但是... - Shivam
React 不使用显式的事件监听器,所以它似乎与实际的问题帖子没有任何关系? - Mike 'Pomax' Kamermans
1
在最后添加依赖项 => useCallback(() => {}, [ ]) - undefined
正如@vemga6在上面提到的,最后的依赖关系非常关键,否则这个方法就行不通。 - undefined

19

看起来没有人涵盖了DOM规范的这一部分(浏览器和Node.js都实现了),它现在提供了一种机制,可以在不使用removeEventListener的情况下删除事件监听器。

如果我们查看https://dom.spec.whatwg.org/#concept-event-listener,我们会发现在设置事件监听器时可以传递许多属性选项:

{
    type (a string)
    callback (an EventListener object, null by default)
    capture (a boolean, false by default)
    passive (a boolean, false by default)
    once (a boolean, false by default)
    signal (an AbortSignal object, null by default)
    removed (a boolean for bookkeeping purposes, false by default)
}

在这个列表中有很多有用的属性,但是为了移除一个事件监听器,我们需要使用其中的signal属性(它在2020年末添加到DOM级别3),因为它让我们可以使用AbortController来移除事件监听器,而不必去麻烦地保留对确切处理函数和监听器选项的引用,“否则removeEventListener甚至都无法正常工作”:

const areaListener = new AbortController();

area.addEventListener(
  `click`,
  ({clientX: x, clientY: y}) => {
    app.addSpot(x, y);
    app.addFlag = 1;
  },
  { signal: areaListener.signal }
);

现在,当我们需要移除事件监听器时,只需运行:

areaListener.abort()

好了:JS引擎将中止并清除我们的事件监听器。无需保留对处理函数的引用,也无需确保我们调用removeEventListener时使用与所调用addEventListener相同的函数和属性:我们只需通过单个、无参数的中止调用取消监听器。

当然,如果我们想要这样做“因为我们只想让处理程序触发一次”,那么我们甚至不需要这样做,我们可以使用{ once: true }创建一个事件监听器,JS会处理其余事项。无需删除代码。

area.addEventListener(
  `click`,
  () => app.bootstrapSomething(),
  { once: true }
);

3
好的信息。很高兴看到在问题发布9年后仍有答案出现。 - Jinu Joseph Daniel
JS总是在不断变化,有些主题值得跟进:仅几年前的唯一解决方案和正确答案今天可能已经完全过时了,这在这里肯定是个事实(特别是IE产品线只剩下几个月的时间)。最后 =) - Mike 'Pomax' Kamermans
1
今天我学到了新东西。如果这确实解决了我的问题,我会回报的。 - Alex Cory
2
嘿,我做到了:] 我很高兴你觉得这个有用。如果你对EventTarget有更多的功能请求,请在GitHub上询问(在搜索现有请求后!)。 - Benjamin Gruenbaum
1
整个 JS 世界向您致敬! - Mike 'Pomax' Kamermans
显示剩余2条评论

10

你在两次调用中创建了两个不同的函数。因此,第二个函数与第一个函数没有任何关系,引擎可以移除该函数。请改用共同的标识符来表示该函数。

var handler = function(event) {
              app.addSpot(event.clientX,event.clientY);
              app.addFlag = 1;
          };
area.addEventListener('click', handler,true);

稍后,您可以通过调用以下方法来删除处理程序:
area.removeEventListener('click', handler,true);

5

如果想要移除它,请将函数存储在变量中,或者直接使用一个命名函数并将其传递给 removeEventListener 方法调用:

function areaClicked(event) {
    app.addSpot(event.clientX, event.clientY);
    app.addFlag = 1;
}

area.addEventListener('click', areaClicked, true);
// ...
area.removeEventListener('click', areaClicked, true);

2
但是我该如何将参数(这里是事件)传递给那个函数呢?这就是为什么我使用匿名函数的原因。 - Jinu Joseph Daniel
1
它是由浏览器传递的。无论您是否单独定义函数都没有关系。 - ThiefMaster
3
警告:我发现了我的方法有什么问题。removeEventListener() 方法只能与命名函数一起使用,无法与匿名函数一起使用!当我编辑代码时考虑到这一点后,一切都按计划进行。您必须在闭包中定义一个命名函数,并返回传递给闭包的参数的实例引用。这样做,removeEventListener() 就可以完美地工作了。 - David Edwards

2
如果您想将局部变量传递给由事件监听器调用的函数,可以在函数内部定义该函数(以获取局部变量),并在函数本身中传递函数名称。例如,让我们从将app作为局部变量添加到事件监听器的函数开始。您可以在此函数内部编写一个函数,例如:
function yourFunction () {
    var app;

    function waitListen () {
        waitExecute(app, waitListen);
    }

    area.addEventListener('click', waitListen, true);
}

当调用waitExecute时,您需要的是能够删除它的内容。
function waitExecute (app, waitListen) {
    ... // other code
    area.removeEventListener('click', waitListen, true);
}

我在这里遇到了一个问题。即使您定义了事件处理程序函数,保存了对该函数的引用,然后稍后将该引用传递给removeEventListener(),该函数也不会被删除。评论框太小无法发布代码,如果您需要代码,我必须使用一个答案框... - David Edwards
以上的补充说明:另一个有趣的现象是,即使您指定了事件监听器为被动的,旧的事件监听器仍然存在于链中。更糟糕的是,旧的事件监听器现在变成了阻止事件处理程序,而新的事件监听器保持其被动状态。我认为这里需要解释一下。 - David Edwards

1

首先定义您的事件处理程序,

然后执行操作。

area.addEventListener('click',handler);
area.removeEventListener('click',handler);

1
对于未来的访问者:我们曾经需要这个,但是从2020年开始,JS有一个可以使用的中止信号,因此您不再需要removeEventListener了。参考链接 - Mike 'Pomax' Kamermans

1

我在window中使用全局变量来存储监听器回调函数。

window.logoutListener = () => {
  // code
};

document.addEventListener('click', window.logoutListener, {
   capture: true,
});

// ------- In other file, out of context --------
document.removeEventListener('click', window.logoutListener);

removeEventListener() 没有起作用的原因是因为第三个参数必须相同。

因此,为事件监听器选项指定相同的签名就可以解决问题了。

document.removeEventListener('click', window.logoutListener, {
  capture: true,
});

1

我最近也遇到了同样的问题。我找到一个合理的解决方案是从HTMLElement类中删除元素上的"onclick"属性。

假设您已经从DOM获取了组件 - 使用document.getElementByIddocument.querySelector - 您可以尝试这个代码:

js

const element = document.getElementById("myelement");
element.attributes.removeNamedItem('onclick');

HTML 示例

<div onClick="memoryGame.flipCard(this)">
   .... // children elements
</div>

我知道这个解决方案并不是最好的,但它能够工作!

希望我能够帮到你。

祝好!

附言:请给我一个“有用的答案”...谢谢:D


很高兴看到即使过了10年,这个帖子仍然活跃。 - Jinu Joseph Daniel
@JinuJosephDaniel,没错啊。我们总是可以从另一个角度学到新的东西或者提高自己。这个解决方案对你来说怎么样?!? - Marlon Klagenberg
不确定这是否是一个合理的解决方案,因为它依赖于一种不应再使用的事件监听器。只需使用“新”的中止信号 =) - Mike 'Pomax' Kamermans

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