JavaScript 事件循环乱序执行

标签 javascript settimeout event-loop

我正在尝试书中的一个示例来确认 JavaScript 事件循环是如何工作的,这里是代码

const baz = () => console.log("baz");
const bar = () => console.log("bar");

const foo = () => {
    console.log("foo");
    setTimeout(bar, 0);
    baz();
}
foo();
setTimeout 在这里的工作方式很简单(乱序执行)输出是
foo
baz
bar 
我不明白的是我添加一行时的顺序
const baz = () => console.log("baz");
const bar = () => console.log("bar");

const foo = () => {
    console.log("foo");
    setTimeout(bar, 0);
    baz();
}
setTimeout(baz, 0); // this somehow runs before foo() is finished
foo();
输出是
foo
baz
baz 
bar
如何在 foo() 完成之前第二次 setTimeout 冲洗?

最佳答案

这是从事件循环的 Angular 进行的解释。
您可以可视化调用堆栈,它用于跟踪我们在给定时间点在程序中的位置。当你调用一个函数时,我们将它压入堆栈,当我们返回/完成一个函数时,我们将它从堆栈顶部弹出。最初,堆栈是空的。
当你第一次运行你的代码时,你的主“脚本”将被插入堆栈,并在脚本完成执行后从堆栈中弹出:

Stack:
------
- Main()   // <-- indicates that we're in the main script
然后我们定义了一些函数baz , barfoo ,并最终到达我们的第一个函数调用,setTimeout(baz, 0) ,因此,我们将其压入堆栈:
Stack:
------
- setTimeout(baz, 0)
- Main()
setTimeout()0 之后启动一个 web-api ms 将您的 baz 排入队列回调到任务队列。之后 setTimeout已经把它的工作交给了 web-api,它的工作已经完成了,所以它已经完成了它的工作并且可以从堆栈中弹出:
Stack:
------
- Main()

Task Queue: (Front <--- Back)
baz
事件循环的工作是从任务队列中取出任务并在堆栈为空时将它们压入堆栈。目前,堆栈不是空的,因为我们仍在主脚本中,所以 baz()尚未执行。我们遇到的下一个函数调用是 foo() ,所以我们将它压入我们的堆栈:
Stack:
------
- foo()
- Main()

Task Queue: (Front <--- Back)
baz
Foo 然后调用控制台对象的 log()方法,它也被压入堆栈:
Stack:
------
- log("foo")
- foo()
- Main()

Task Queue: (Front <--- Back)
baz
此日志 "foo" , 和 log()完成工作后从堆栈中弹出。然后我们继续单步执行函数 foo。我们现在遇到了对 setTimeout(bar, 0); 的函数调用。 .这很像第一个函数调用,推送 setTimeout(bar, 0)到堆栈上。这衍生了一个添加 bar 的 web-api到任务队列。 setTimeout(bar, 0)一旦它把它的工作交给 web-api 也完成了,所以它也会从堆栈中弹出(这些步骤参见第二和第三个 ascii 图),给我们留下:
Stack:
------
- foo()
- Main()

Task Queue: (Front <--- Back)
baz, bar
最后,我们到达函数 foo 的最后一行。调用 baz() .这插入了baz()到调用堆栈,然后压入 log("baz")到调用堆栈的顶部,日志“baz” .到目前为止,我们已经记录了“foo”,然后是“baz”。在 baz 被记录后 log()被从堆栈中弹出,baz() 也是如此。因为它已经完成了。
foo() 中的最后一行完成后,我们隐式返回,弹出 foo()离开堆栈,给我们留下 Main() .一旦我们从 foo 返回,我们的控制/执行在 where foo() 之后返回到主脚本。被调用。由于我们的脚本中没有更多函数可以调用,我们弹出 Main()离开堆栈,给我们留下:
Stack:
------
<EMPTY>

Task Queue: (Front <--- Back)
baz, bar 
现在堆栈是空的,事件循环可以进来处理bazbar在任务队列中。首先是需要baz出队列并将其压入堆栈,然后调用 log("baz") , 推 log到堆栈上,然后 记录“baz” .日志完成后,logbaz从堆栈中弹出,使其再次为空:
Stack:
------
<EMPTY>

Task Queue: (Front <--- Back)
bar 
现在堆栈又是空的,事件循环从队列中取出第一个任务(即:bar)并将其插入堆栈。 bar然后调用log("bar") ,增加了 log("bar")到堆栈,以及日志“酒吧”到控制台。记录完成后,log()bar()都从堆栈中弹出。
因此,您的日志输出按以下顺序打印(请参阅上面的粗体日志):
"foo"
"baz"
"baz"
"bar"
可以在 here 找到一些关于事件循环和调用堆栈的好资源, herehere .

关于JavaScript 事件循环乱序执行,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/64820774/

相关文章:

javascript - 我想以一定的间隔在 Canvas 上绘制,但 setTimeout 不起作用

javascript - LaterJS 中的多个 setTimeouts 不遵守计划

c++ - 当在错误槽中启动事件循环时发生 ContentNotFoundError 时,QNetworkReply 会发出两次错误信号

Javascript window.onresize 似乎不提供不止一个更新?

javascript - 无法使用 Google Maps API V3 删除 MVCArray/Polylines

javascript - 如何将 setTimeout() 与 React Hooks useEffect 和 setState 一起使用?

python - 如何取消asyncio的loop.call_later()?

javascript - javascript 中的一切都可以异步吗?

javascript - 我如何避免在 JavaScript 中首先使用 parseFloat/Int, number()?

javascript - Highcharts - 在点击时显示工具提示而不是悬停