为什么在单击事件侦听器内触发 click() 不会导致无限循环?

Tus*_*wal 82 html javascript addeventlistener

有人可以解释一下这段 JavaScript 代码的程序流程:

const $leaveRoom = document.querySelector('#leave-button');
let a = 1;
$leaveRoom.addEventListener('click', () => {
  console.log(a);
  console.log("check");
  a++;
  $leaveRoom.click();
  console.log(a);
  a++;
});
Run Code Online (Sandbox Code Playgroud)
<button id="leave-button">Leave Room</button>
Run Code Online (Sandbox Code Playgroud)

The Output was:
1
check
2
check
3
4
Run Code Online (Sandbox Code Playgroud)

这个问题可能听起来很愚蠢,但我是 JavaScript 的新手。我无法理解这段代码的程序流程。我想知道我是如何在输出中得到 3 & 4 的。

Mis*_*ojo 53

这个问题的关键是每个方法上都有一个隐藏的标志element.click()

每个元素都有一个关联的点击进行中标志,该标志最初未设置。

文档:https : //html.spec.whatwg.org/multipage/interaction.html#dom-click

一旦这个方法被激活,这个标志就会从progess Status == unsetprogess Status == active (伪代码)

(然后一旦它包含的代码被完全执行,它就会返回到它的初始状态)

当此标志处于该active状态时,将忽略对该方法的任何调用。


这是我原来的帖子,展示了`console.log()`的执行顺序

const bt_leaveRoom = document.querySelector('#leave-button')
var counter = 0
var origin  = 'event clic'

bt_leaveRoom.addEventListener('click', () => 
  {
  let source = origin
  console.log(`_first console.log(): counter = ${ ++counter }, origin = ${source}`)

  origin = 'call'
  bt_leaveRoom.click()
  
  console.log(`second console.log(): counter = ${ ++counter }, origin = ${source}`)
  })
Run Code Online (Sandbox Code Playgroud)
<button id="leave-button">Leave Room</button>
Run Code Online (Sandbox Code Playgroud)

如果我以这种方式编码,则隐藏标志的行为方式相同:
将此行替换
bt_leaveRoom.click()
为:
if (source !== 'call') bt_leaveRoom.click()

但实际上系统使用了隐藏标志(名为progress flag?)的方法
,它可以(在伪代码中)

if (progress_flag_of_bt_leaveRoom.click() is unset) do { bt_leaveRoom.click() }  
Run Code Online (Sandbox Code Playgroud)

  • 这并不能回答问题。为什么在函数内调用 $leaveRoom.click() 不会创建无限循环?基本上,第二个 console.log() 语句似乎永远不应该被执行 (3认同)
  • 你明白了,点击进度标志是关键。在你的答案中让这一点更加突出怎么样?就像一开始一样,明确引用了规范“如果设置了此元素的点击进度标志,则返回”。并提醒您调用 HTMLElement.click() 会同步调用事件处理程序?那么为什么不删除其余的呢,因为它确实没有增加太多呢? (3认同)
  • @TusharAgarwal“setTimeout”在单独的进程中工作,即使选择的延迟为零,此调用也足够晚以完成所有要执行的指令,因此该“Flag”返回到其“之前取消设置状态。在他的示例中,“setTimeout”是最后一条指令,这足以使“Flag”返回到“unset”。另一方面,如果后面还有其他指令,那么“Flag”将不会返回到“unset”,并且调用将被拒绝。除非你在“setTimeout”中设置了足够长的延迟 (3认同)

Gha*_*chi 13

我尝试了几件事来找到这个问题的答案。对于这里发生的事情,我还没有真正找到明确的答案,但我相信我在这里分享的内容可以解决这个极好的问题。

我简化了代码以专注于事件的递归触发。

简化代码

const $leaveRoom = document.querySelector('#leave-button');
let a = 1;
$leaveRoom.addEventListener('click', () => {
    console.log(a++);
    $leaveRoom.click();
});
Run Code Online (Sandbox Code Playgroud)
<button id="leave-button">Leave Room</button>
Run Code Online (Sandbox Code Playgroud)

我们在这里可以看到,我们有两次调用console.log,第一个是实际点击按钮,第二个是调用$leaveRoom.click();。它似乎出于某种原因停在那里。

使用 dispatchEvent

const $leaveRoom = document.querySelector('#leave-button');
let a = 1;
$leaveRoom.addEventListener('click', () => {
    console.log(a++);
    $leaveRoom.dispatchEvent(new Event('click'));
});
Run Code Online (Sandbox Code Playgroud)
<button id="leave-button">Leave Room</button>
Run Code Online (Sandbox Code Playgroud)

在这里,该事件被多次触发(对我来说是 44 次),这可能是由于您的机器有多快。不过它似乎最终会停止触发,所以我认为这里也会发生同样的现象。

使用 setTimeout

const $leaveRoom = document.querySelector('#leave-button');
let a = 1;
$leaveRoom.addEventListener('click', () => {
    console.log(a++);
    setTimeout(() => { $leaveRoom.click(); });
});
Run Code Online (Sandbox Code Playgroud)
<button id="leave-button">Leave Room</button>
Run Code Online (Sandbox Code Playgroud)

如果你正在寻找一种无限触发点击事件的方法,不管前面的方法为什么会失败。这似乎确实可以解决问题。

说了这么多,我仍然不知道这个隐藏的力量会阻止之前方法的递归。也许有人可以对此有所了解。

  • 正如您所说@Bergi,这绝对取决于引擎。在 Chrome 和 Edge 中,我得到 44 计数,然后将错误记录到控制台“超出最大调用堆栈大小”。在 FireFox 中,它运行 383 次,然后在控制台中记录错误“未定义”。 (5认同)
  • 那44次确实很可疑。我想知道是什么原因导致了这个限制。 (3认同)
  • 是的,不过取决于引擎。我猜这是一些内部队列或堆栈大小限制。 (3认同)