JavaScript事件循环澄清

6
我一直看到有关“Javascript事件循环”(即浏览器JS运行时事件循环)的解释,但这些解释似乎不可信,我希望有人能提供权威的澄清。
我的基本假设是,JS事件循环就像我们几十年来在UI框架中一直使用的事件循环,类似于:
  // [... some initialization ...]

  // The Event Loop
  while (true) {
    if (! EventQueue.isEmpty()) {
       event = EventQueue.pop_oldest_item();
       event.callback(event [or some other kind of args]);
    }
    // [... defer to other non-JS tasks...]
  }

但我一直看到像下面这样的解释(示例见下文):
事件循环:
1. 检查(Javascript)调用栈是否为空。 2. 检查回调队列[又称EventQueue]是否为空。 3. 如果调用栈为空且回调队列不为空,则: a. 出队最旧的回调队列项。 b. 将该回调函数推入调用栈(不提及调用该函数)。 4. 继续循环。
这显然模糊地遵循了我上述的假设模型,但有两个关键而令人困惑的差异:
A. 为什么事件循环需要检查JS调用栈是否为空?毕竟每次循环时,调用栈的状态都将相同(无论它是否完全“空”都无关紧要——它不需要“检查”)。上一次调用的任何函数都会返回,恢复堆栈。因此,这部分没有意义。
B. 为什么事件循环“将回调推入JS堆栈”?事件循环不应该只是调用函数,从而创建一个合法的堆栈帧和从函数返回的方法,更不用说实际执行函数了吗?
所以我希望能得到一个澄清,解释这些解释为什么是正确的,或者加强我非常怀疑它们不正确的观点。
这些事件循环解释的例子来源包括:
- Philip Roberts在14:00的"What the heck is the event loop anyway?"演讲,链接为https://youtu.be/8aGhZQkoFbQ?t=839。 - Typescript High Performance(书籍)第83页。 - "What is the Javascript event loop?",链接为http://altitudelabs.com/blog/what-is-the-javascript-event-loop/。 - "Understanding Javascript Function Executions - Call Stack, Event Loop, Tasks & more",链接为https://medium.com/@gaurav.pandvia/understanding-javascript-function-executions-tasks-event-loop-call-stack-more-part-1-5683dea1f5ec
2个回答

1
这是我对你的问题的回答:
JavaScript以单线程和同步的方式运行,因此事件回调函数将在“全局执行上下文”弹出执行堆栈后执行。所有事件都将添加到所谓的“事件队列”中。
全局执行上下文完成所有执行后,JS引擎将继续检查是否存在事件队列中的任何事件。如果JS引擎看到有事件,则会为回调函数创建一个新的执行上下文,并将其推送到执行堆栈上。
在JS中,每次调用函数时,JS引擎都会创建一个“执行上下文”,它创建一个私有作用域,在该作用域内声明的任何内容都无法直接从当前函数作用域之外访问,并将其推送到执行上下文堆栈的顶部。函数执行完毕后,执行上下文将被弹出。

2
这是我对你的回答的评论。 - cambunctious
我感谢你的回答,但我不确定这如何解决我的问题。你确认存在一个事件队列--这是毋庸置疑的。并且你描述了函数调用的工作原理--就像在任何语言中一样,它涉及堆栈帧(这里称为执行上下文)。还有一些关于全局执行上下文从堆栈中弹出的内容--这没有意义。简而言之,你没有解决我的"A"和"B"点。但还是感谢你的努力。 - gwideman

0
由于调用栈中执行的某些函数(事件处理程序、setTimeout、http请求)具有异步性质,因此这些操作的消息(回调)可以在任何时候添加到消息队列中。这可能发生在主线程仍在运行调用栈中的函数时,因此事件循环需要检查它是否为空。一旦为空,它将从消息队列中提取下一个消息并将其放入调用栈中。这个循环构成了事件循环的本质。
我制作了一个视频演示来解释和演示这个过程: https://www.youtube.com/watch?v=4xsvn6VUTwQ

至于B部分 - 这就是Javascript运行时的架构。函数在调用堆栈中执行,这实际上是Javascript中单个执行线程。事件循环不直接调用函数,而是协调消息执行的时间和顺序。 - jspru
感谢您的回答,但就我所知,您的回答恰好展示了我对事件循环工作原理解释的疑问。什么是“在调用堆栈上抛出消息”?调用堆栈包含每个调用的上下文数据(参数、局部变量),而该调用正在执行时。如果该消息不是实际调用的一部分,即:调用函数,那么“在调用堆栈上抛出消息”是如何与之相符的呢?您声称这不是事件循环所做的事情。 - gwideman

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