我知道当呼叫栈为空时,消息会从队列进入呼叫栈。但是,如果事件循环可以将消息从队列直接推送到调用堆栈而不用等待,那会更好吗?此行为背后的原因是什么?如果事件循环会在确切的时间推送消息,那么我们总是可以依靠setTimeout等功能。
setTimeout(() => console.log("I want to be logged for 10ms, but I will never be :("), 10);
// some blocking operations
for(let i = 0; i < 500000000; i++){
Math.random() * 2 + 2 - 3;
}
console.log("I'll be logged first lol");
由于一致性原因,它可能永远不会更改,但我仍然很好奇。也许我没看到什么,等待空堆栈这一概念背后存在严重的技术原因。您是否可以访问一些有关JS中的体系结构决策的文章,或者您知道必要的行为时的基本示例?关于JS如何工作的文章很多,但是我找不到“为什么事件循环完全那样工作”这样的东西。任何帮助将不胜感激。
答案 0 :(得分:5)
V8开发人员在这里。这个问题似乎是基于对“调用栈”是什么的误解:它不是任何人都可以将其推入其中的数据结构。相反,它是一堆函数相互调用时当前状态的术语。将另一个函数“推”到调用堆栈上的唯一方法是当前执行的函数调用它时。如果事件系统在函数中的任意位置插入随机调用,那将导致一个非常奇怪的编程模型。
您可以设计一个概念上类似的编程环境,但是与其将任何内容压入调用堆栈,不如将其中断并挂起当前正在执行的所有内容,然后执行setTimeout
调度的函数(或事件)处理程序等),然后再恢复之前的执行。您需要解决的一个问题是:如果重复执行该操作,即如果该调度函数被另一个调度函数中断,又又被另一个调度函数中断,该怎么办?如果计划的功能要永远完成,该怎么办:先前执行的代码何时才能重新取得进展?同样,尽管这可以在单线程环境中完成,但随机中断是并发的(从一致性的角度来看,这等同于并行/多线程),因此您需要诸如锁之类的同步原语(本质上是,具有函数说“请勿中断此部分”的一种方式-依次意味着您实际上无法保证调度请求的准确性)。不要低估了这一切会给程序员带来的复杂性成本:编写代码时,他们必须牢记任何东西都可能在任何时候被打断,而另一方面,一个函数可能想要处理的任何数据可能尚未准备好,因为产生它的另一个函数尚未完成运行。
简而言之,JavaScript的事件循环系统就是这样,因为该语言避免了并发,并且即使在单线程系统上,随机中断执行其他函数的功能也是并发的。