removeEventListener - 在函数定义内部的回调函数?

3

我有以下代码,其中我向文档添加了事件侦听器,然后将其删除。

document.addEventListener("keypress", gameStart);

function gameStart() {
    document.querySelector("h1").innerHTML = "Level 1";
    document.querySelector("h2").style.visibility = "hidden";
    document.removeEventListener("keypress", gameStart);
}

我无法理解如何在gameStart()定义内的removeEventListener方法中回调gameStart。这对我来说似乎是循环的,但我感觉我在这里误解了一些基本的东西。我错过了什么?


该函数不知道它被用作回调函数。gameStart是对该函数的引用,通过引用,在JS中可以传递函数。对于命名函数,函数本身的引用会自动传递给它们,并且如果将匿名函数分配给变量,则可以从外部作用域获取该引用。这个引用实际上是一个值,在JS中,循环引用不是一个问题,你可以轻松地执行例如 const arr = []; arr[0] = arr;。请参考https://dev59.com/cHI_5IYBdhLWcg3wK_3E。 - Teemu
如果你对此感到不舒服,也可以传递第三个参数options给addEventListener,其中你可以指定监听器只会被调用一次。document.addEventListener("keypress", gameStart, {once: true});。文档:https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/addEventListener - ItsFlo
4个回答

6
removeEventListener文档中,我们可以看到:
target.removeEventListener(type, listener[, options]);
target.removeEventListener(type, listener[, useCapture]);

...

listener
   The EventListener function of the event handler to remove from the event target.

在调用removeEventListener函数时,EventListener函数(在本例中是gameStart)不会被调用,因此不存在任何循环调用或递归,它被传递给removeEventListener函数,这样该函数就可以从该事件中注销。

现在对我来说更有意义了,但我不明白的是你说removeEventListener从不调用gameStart,但是addEventListener如何知道在完全相同的语法下(即在两种方法中它们都出现为gameStart)调用gameStart呢?通过语法没有办法让我知道它是回调还是其他情况吗? - codingbryan
addEventListener也不会调用函数;实际上,如果没有keypress(关于keypress事件),该函数将永远不会被调用。addEventListener只是将该函数与特定事件进行了挂钩,而removeEventListener则从该事件中解除挂钩该函数。 - Christos Lytras

4

每个人的答案都比较复杂,简单点来说:

1- 在 document.removeEventListener("keypress", gameStart); 中,你没有调用 gameStart 函数,而是实际上调用了 removeEventListener() 函数。现在再读一遍,因为我知道这可能会让人困惑。

2- 你告诉 removeEventListener() 函数从你的 gameStart() 函数中删除按键按下的监听器。但是 gameStart 在那行代码末尾从未被调用过。

3- 现实生活中的例子(与计算机无关):

有人打电话给你的号码 xxx-xxx-xxxx,然后你接听并且那个人告诉你做某件事情(这时候的你就相当于是 gameStart),当你完成了任务之后,你想挂断电话因为你已经没有其他事情需要和对方讨论了,所以你告诉对方替你挂断电话,否则他们将永远留在电话中(另一个人就是事件监听器)。你没有挂断电话,甚至没有参与挂断电话的动作,你只是告诉对方他们需要做什么。

希望这有所帮助!


点2不应该是removeEventListener()方法从文档(即EventTarget)中移除事件监听器(即我的情况下的gameStart())吗? - codingbryan
我不确定我是否正确理解了你的评论,但是当你将addEventListener()添加到gameStart()时,事件监听器来自于addEventListener()。当gameStart()触发一个事件时,它在整个页面上可见,并且不会对任何东西产生影响,但是由于addEventListener()的存在,你能够捕获该事件并对其进行一些操作。当你使用removeEventListener()时,你停止了从gameStart()捕获信号,但是gameStart()永远不会停止触发事件,只是你不再捕获它们,所以你甚至不知道它们正在发生。 - Mixael Contreras

4

函数内部使用的变量在调用函数之前不会被求值。

gameStart 因此可以引用自身,因为它在调用之前就被创建。


你能详细说明一下你的第二句话吗?我不太理解。 - codingbryan
我一直在读一本关于JavaScript的书,很高兴地说你写的东西终于对我有意义了! - codingbryan

4
你可以始终传递正在定义的函数的引用,因为函数不必在定义时拥有所有可用内容,而这是在调用时需要满足的条件。这对于递归也是基本的,例如:
function getFactorial(num) {
    if (num <= 2) {
        return num;
    }
    return num * getFactorial(num - 1);
}

也许以下内容可以帮助您更好地理解:
function getType() {
    return typeof getType;
}

上述函数总是返回 "function"
接下来另一个例子:
function getTypeOfX() {
    return typeof myObj.x;
}

你可以定义这个函数,但是当你使用getTypeOfX()调用它时,你会得到一个错误,因为myObj没有在函数定义的外部/全局范围中定义。
如果你正在控制台中尝试,请执行:
var myObj = {
    x: ""
}

即使在函数定义之后,再次调用getTypeOfX(),就会发现它现在打印"string"

这里的结论是@Quentin提到的:

函数内使用的变量在函数被调用之前不会被评估。


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