JavaScript - 调用堆栈究竟何时变为 "empty"?

JavaScript - When exactly does the call stack become "empty"?

我已经阅读了几个关于事件循环的 posts/SO 线程,并且根据 MDN's article

When the stack is empty, a message is taken out of the queue and processed.

作为一个JS新手,我还比较困惑的是——调用栈到底什么时候变成了"empty"?例如,

<script>
function f() {
  console.log("foo");
  setTimeout(g, 0);
  console.log("foo again");
}
function g() {
  console.log("bar");
}
function b() {
  console.log("bye");
}

f();
/*<---- Is the stack empty here? */
b();
</script>

正确的执行顺序是foo - foo again - bye - bar

但今天我开始思考:在退出 f() 调用后堆栈在技术上不是空的吗?我的意思是那时我们不在任何函数中,我们还没有开始任何新的执行,所以在继续 b(),并给出 foo - foo again - bar - bye?

的顺序

如果我们有 a million lines of code 或一些密集的计算要执行,而 setTimeout(func, 0) 只是在队列中停留了很长时间怎么办?

当当前正在执行的 Javascript 部分已经完成并且没有更多的顺序指令要执行时,然后 JS 引擎才会从事件队列中拉出下一个项目。

因此,在您的示例中:

f();
b();
// JS is done executing here so this is where the next item will be
// pulled from the event queue to execute it

Javascript 是 single-threaded,这意味着 Javascript 的当前线程运行到完成,执行一个序列中的所有指令,直到它到达代码末尾。然后,并且只有在那时,它才会从事件队列中拉出下一个项目。

以下是一些可能有助于您理解的其他答案:

How Javascript Timers Work

How does JavaScript handle AJAX responses in the background?(在此 post 中引用了一大堆事件循环)

Do I need to be concerned with race conditions with asynchronous Javascript?

Can JS event handlers interrupt execution of another handler?

我能想到的最好的解释方法是,在代码完成 运行所有相关路径之前,调用堆栈不为空。 setTimeout 为 0 只是将您的代码推到堆栈的末尾。

当代码 运行s 在 运行 时, 成为 运行 的所有内容都是调用堆栈的一部分,顺序将根据调用的顺序和调用的任何 timeouts/intervals/async 方法进行调整。

一些示例:

function foo() {
  console.log('foo');
}

function bar() {
  baz();
  console.log('bar');
}

function baz() {
  setTimeout(function() { console.log('timeout') }, 0);
  console.log('baz');
}

foo();
baz();
// call stack ends here, so, timeout is logged last.

// in console
// foo
// baz
// timeout

如您所见,bar 不包含在 运行 时间堆栈中,因为它未被调用。如果我们有一些 HTML:

<div onclick="bar()">Bar runs</div>

当您单击 div 时,您将看到 bazbar,然后 timeout 记录到控制台,因为超时总是推到最后当前 运行 宁 processes/call 堆栈。

希望这个解释对您有所帮助!

最简单的解释:当当前脚本、函数或事件处理程序中的所有同步代码都已完成时运行。

直接回答 "what if I have millions of lines..." 是 - 你的 setTimeout 电话被困在队列中,将等待轮到它。

虽然 <script> 标签中的代码块没有包含在显式函数中,但将其视为浏览器告诉 javascript 的全局函数可能会有所帮助要执行的运行时。因此,在脚本块中的代码执行完毕之前,调用堆栈不会为空。