JavaScript事件循环无序执行
我尝试了一本书中的一个例子来确认JavaScript事件循环是如何工作的,下面是代码JavaScript事件循环无序执行,javascript,settimeout,event-loop,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(); se
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漂洗?如果将异步操作与代码的其余部分分开,您可以清楚地看到:
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();
如果我们提取setTimeout
函数,我们将拥有:
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
在第一个循环期间,setTimeout
中定义的异步函数已添加到下一个循环中,因此我们必须执行它们:
setTimeout(baz, 0);
setTimeout(bar, 0);
产生下面两行日志:
baz
bar
您需要记住,所有同步代码将首先运行,然后是异步代码。(
setTimeout
将始终对异步操作进行排队,即使超时设置为0。)
因此,考虑到这一点,事件的顺序如下:
- 注册对
baz()的异步调用
- 同步输出
foo
- 注册对
bar()的异步调用
- 同步调用
,输出baz()
baz
foo
,然后是baz
然后我们的异步事件运行,依次输出
baz
和bar
。调用setTimeout(baz,0)
函数,它将进入调用堆栈,然后进入事件循环中的计时器阶段,并在那里等待
调用foo()
函数后,将console.log(“foo”)
插入调用堆栈,setTimeout(bar,0)
和baz()
。
由于console.log(“foo”)
是一个同步操作,它会立即执行,您可以在输出中看到“foo”
。
setTimeout(条形图,0)
转到事件循环中的计时器阶段并等待。
然后执行baz()。
执行同步操作后,调用堆栈为空,计时器等待结束,开始执行来自设置超时的回调
setTimeout(baz,0)
->baz
->console.log(“baz”)
=“baz”
在otput中
然后呢
setTimeout(bar,0)
->bar
->console.log(“bar”)
=“bar”
在otput中这里是从事件循环角度的解释
您可以可视化调用堆栈,它用于跟踪在给定时间点我们在程序中的位置。当你调用一个函数时,我们把它推到堆栈上,当我们返回/完成一个函数时,我们把它从堆栈顶部弹出。最初,堆栈是空的
当您第一次运行代码时,您的主“脚本”将被推送到堆栈上,并在脚本完成执行后从堆栈中弹出:
Stack:
------
- Main() // <-- indicates that we're in the main script
setTimeout()
启动一个web api,它在0
ms之后将您的baz
回调排入任务队列。setTimeout
将其工作传递给web api后,其作业完成,因此它已完成工作并可以从堆栈中弹出:
Stack:
------
- setTimeout(baz, 0)
- Main()
Stack:
------
- Main()
Task Queue: (Front <--- Back)
baz
Stack:
------
- log("foo")
- foo()
- Main()
Task Queue: (Front <--- Back)
baz
Foo然后调用console对象的log()
方法,该方法也被推送到堆栈上:
Stack:
------
- setTimeout(baz, 0)
- Main()
Stack:
------
- Main()
Task Queue: (Front <--- Back)
baz
Stack:
------
- log("foo")
- foo()
- Main()
Task Queue: (Front <--- Back)
baz
最后,我们到达调用baz()
的函数foo
的最后一行。这会将baz()
推到调用堆栈上,然后将log(“baz”)
推到调用堆栈的顶部,调用堆栈将记录“baz”。到目前为止,我们已经记录了“foo”和“baz”。在baz被记录之后,log()
将从堆栈中弹出,完成后,baz()
也将弹出堆栈
一旦foo()
中的最后一行完成,我们将隐式返回,从堆栈中弹出foo()
,留下Main()
。一旦我们从foo返回,在调用foo()
之后,我们的控制/执行将返回到主脚本。由于脚本中没有更多的函数可调用,我们从堆栈中弹出Main()
,留下以下内容:
Stack:
------
- foo()
- Main()
Task Queue: (Front <--- Back)
baz, bar
Stack:
------
<EMPTY>
Task Queue: (Front <--- Back)
baz, bar
现在堆栈再次为空,事件循环从队列(即:bar
)中获取第一个任务并将其推入堆栈bar
然后调用log(“bar”)
,这会将log(“bar”)
添加到堆栈中,并将日志“bar”添加到控制台中。日志记录完成后,log()
和bar()
都会从堆栈中弹出
因此,日志的输出将按以下顺序打印(请参见上面的粗体日志):
可以在事件循环和调用堆栈上找到一些好的资源。将事件循环视为一个队列,您的代码在调用foo之前插入一些稍后要做的事情,调用foo,foo在队列中附加一些其他内容,foo结束,事件循环从队列中提取第一次提交。啊,我明白了,我现在明白了,非常感谢