【问题标题】:JavaScript - When exactly does the call stack become "empty"?JavaScript - 调用堆栈究竟何时变为“空”?
【发布时间】:2016-04-14 14:28:54
【问题描述】:

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

当堆栈为空时,从队列中取出一条消息并 已处理。

作为一个 JS 新手,我仍然感到困惑的是——调用堆栈究竟什么时候变成“空”的?例如,

<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() 之前,不应该处理 setTimeout 调用消息(已立即排队) ,并给出foo-foo again-bar-bye的顺序?

如果我们有 a million lines of code 或一些密集的计算要执行,而 setTimeout(func, 0) 只是在队列中等待多长时间?

【问题讨论】:

  • 我能想到的最好的解释方法是,在代码完成运行所有相关路径之前,调用堆栈不是空的。 setTimeout 为 0 只会将您的代码推送到堆栈的末尾,即:在 b() 之后。
  • 还有b 需要执行,所以f 之后的堆栈不为空。 b之后还有什么可做的?
  • @Jacques 我认为堆栈的概念应该根据函数调用/帧来理解,而不是还有代码要执行......我从MDN examplethis demo...
  • 这个视频对这个话题很有帮助:vimeo.com/96425312 简而言之,超时事件在剩余的脚本执行之后才会发生。
  • 由于全局环境是一个执行环境,我想它在脚本最初加载时位于调用堆栈的底部。

标签: javascript asynchronous concurrency settimeout event-loop


【解决方案1】:

当当前正在执行的 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 是单线程的,这意味着 Javascript 的当前线程运行完成,执行序列中的所有指令,直到它到达代码的末尾。然后,只有这样,它才会从事件队列中拉出下一个项目。

这里有一些其他的答案可以帮助你理解:

How Javascript Timers Work

How does JavaScript handle AJAX responses in the background?(这篇文章中有一大堆事件循环引用)

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

Can JS event handlers interrupt execution of another handler?

【讨论】:

    【解决方案2】:

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

    当代码在运行时运行时,运行的所有内容都是调用堆栈的一部分,顺序将根据调用的顺序以及任何超时/间隔/异步方法进行调整调用。

    一些例子:

    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 记录到控制台,因为超时总是被推到当前正在运行的进程/调用堆栈的末尾。

    希望这个解释有帮助!

    【讨论】:

    • 谢谢——所以在我的例子中,在f() 完成后,堆栈中究竟有什么
    • b() 和任何未运行的超时。 (在您的示例中将是所有这些。)这样想,将运行的每个语句都在您的调用堆栈中,按照它们在您的代码中被调用的顺序。唯一不按此顺序的情况是超时/间隔/异步方法。
    • "setTimeout 为 0 只会将您的代码推送到堆栈的末尾" - 更准确的说法是它被推送到排除 queue,而不是调用堆栈 - 这是两个不同的概念(jfriend00 的回答很好地解释了这一点)。
    • @Kobi 我宁愿不争论语义点。你所说的队列堆栈,堆栈需要运行的语句队列。
    • @Kobi 的观点是正确的,它并没有错。事件队列和调用栈有很大的区别——即事件队列是先进先出,栈是后进先出
    【解决方案3】:

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

    直接回答“如果我有数百万行...”是的 - 您的 setTimeout 呼叫被卡在队列中,将等待轮到它。

    【讨论】:

      【解决方案4】:

      虽然&lt;script&gt; 标记中的代码块没有包装在显式函数中,但将其视为浏览器告诉javascript 运行时执行的全局函数会很有帮助。因此,在脚本块中的代码执行完毕之前,调用堆栈不会为空。

      【讨论】:

        猜你喜欢
        • 2020-08-01
        • 1970-01-01
        • 2015-08-11
        • 2011-05-05
        • 2014-01-16
        • 2020-05-27
        • 2011-02-23
        • 1970-01-01
        • 2014-09-09
        相关资源
        最近更新 更多