如果您在浏览器控制台中运行这段代码,您认为会发生什么?
function foo() { setTimeout(foo, 0); } foo();
还有这个吗?
function foo() { Promise.resolve().then(foo); } foo();
如果您像我一样,已经阅读了许多有关事件循环,主线程,任务,微任务等的文章,但是很难回答上面的问题-本文适合您。
因此,让我们开始吧。 浏览器中每个HTML页面的代码在
Main Thread中执行。 主线程是浏览器执行JS,重绘,处理用户操作等的主线程。 本质上,这是JS引擎集成到浏览器中的地方。
解决这个问题的最简单方法是查看以下图表:
图1我们看到,任务循环可以进入调用堆栈并完成的唯一位置。 想象你在他的位置。 您的工作就是跟上任务的进度。 任务可以有两种类型:
- 个人-在站点上执行主要的JavaScript代码(以下我们将假定它已经被执行)
- 客户任务-渲染,微任务和任务
您的个人任务很可能会被优先处理。 Event Loop对此表示赞同:)仍然可以简化客户的任务。
当然,首先想到的是给每个客户一个优先级,并将他们排队。 第二个是确切确定如何处理每个客户的任务-一次,一次全部或分批处理。
看一下这个图:
图2基于此方案,构建了事件循环的整个工作。
开始执行任何脚本后,执行此脚本的任务将放入“任务”队列。 在执行此代码时,我们会遇到来自不同客户的任务,这些任务被放入适当的队列中。 在脚本执行任务完成(任务中的任务)之后,事件循环转到微任务(在任务中的任务之后,事件循环从微任务中获取任务)。 事件循环
从他
那里接任务
,直到任务
结束 。 这意味着如果它们的添加时间等于它们的执行时间,那么事件循环将无休止地耙它们。
接下来,他去渲染并执行他的任务。 浏览器优化了Render的任务,如果它认为在此循环中无需重绘任何内容,则事件循环将继续进行。 接下来,Event Loop再次从Tasks中获取任务,并
只请求
一个任务(队列中的第一个任务) ,将其传递给CallStack并在循环中继续进行。
如果其中一个客户没有任务,那么事件循环将转到下一个。 而且,相反,如果客户的任务花费大量时间,则其余客户将等待轮到他们。 而且,如果来自某个客户的任务变得无穷无尽,则Call Stack溢出,浏览器开始宣誓:
图3 现在,我们了解了事件循环的工作原理,是时候弄清楚在本文开头执行代码段之后会发生什么。
function foo() { setTimeout(foo, 0); } foo();
我们看到函数foo通过内部的setTimeout递归调用自身,但是每次调用都会创建一个Tasks客户任务。 我们记得,在事件循环循环中,当从“任务”执行任务队列时,循环中仅需要执行1个任务。 然后还有微任务和渲染中的任务。 因此,这段代码不会导致事件循环遭受痛苦并永远失去其任务。 但是它将在每一圈为客户任务抛出一个新任务。
让我们尝试在Google Chrome浏览器中运行此脚本。 为此,我创建了一个简单的HTML文档,并将其中的这段代码连接到script.js。 打开文档后,转到开发人员工具并打开“性能”选项卡,然后单击“开始分析并重新加载页面”按钮:
图4我们看到“任务”中的任务一次执行一次,大约每4ms执行一次。
考虑第二个难题:
function foo() { Promise.resolve().then(foo); } foo();
在这里,我们看到了与上面示例相同的内容,但是调用foo会添加来自Microtasks的任务,并且它们都完成了直到结束。 这意味着,直到事件循环完成它们之前,他将无法继续移动到下一个客户:(并且我们再次看到了悲伤的
画面 。
在开发工具中查看一下:
图5我们看到微任务大约每0.1ms执行一次,这比Tasks队列快40倍。 全部是因为它们是一次执行的。 在我们的示例中,队列不断移动。 为了实现可视化,我将其减少到100,000次迭代。
仅此而已!
希望本文对您有所帮助,现在您了解了事件循环的工作原理以及上面的代码示例中的工作情况。
再见:)很快再见。 如果喜欢,喜欢并订阅我的频道:)