跳到主要内容

事件循环

浏览器的主线程以其事件循环队列为中心。事件循环负责收集事件(包括用户事件以及其他非用户事件等)、对任务进行排队以便在合适的时候执行回调。然后它执行所有处于等待中的 JavaScript 任务,然后是微任务,然后在开始下一次循环之前执行一些必要的渲染和绘制操作。

以前我们把任务分为微任务和宏任务,现在规范没有宏任务这个说法,称为任务队列.

任务 vs 微任务

一个任务就是指计划由标准机制来执行的任何 JavaScript,如程序的初始化、事件触发的回调等。除了使用事件,你还可以使用 setTimeout() 或者 setInterval() 来添加任务。

任务队列和微任务队列的区别很简单,但却很重要:

  • 当执行来自任务队列中的任务时,在每一次新的事件循环开始迭代的时候运行时都会执行队列中的每个任务。在每次迭代开始之后加入到队列中的任务需要在下一次迭代开始之后才会被执行。
  • 每次当一个任务退出且执行上下文栈为空的时候,微任务队列中的每一个微任务会依次被执行。不同的是它会等到微任务队列为空才会停止执行——即使中途有微任务加入。换句话说,微任务可以添加新的微任务到队列中,这些新的微任务将在下一个任务开始运行之前,在当前事件循环迭代结束之前执行。

阻塞

由于你的代码和浏览器的用户界面运行在同一个线程中,共享同一个事件循环,假如你的代码阻塞了或者进入了无限循环,则浏览器将会卡死。无论是由于 bug 引起还是代码中进行复杂的运算导致的性能降低,都会降低用户的体验。

解决方案

Web_Workers_API

让主线程另起新的线程来运行脚本,这能够缓解上面的情况。一个设计良好的网站或应用会把一些复杂的或者耗时的操作交给 worker 去做,这样可以让主线程除了更新、布局和渲染网页之外,尽可能少的去做其他事情(弊端是不能访问 DOM)。

queueMicrotask

微任务是另一种解决该问题的方案,通过将代码安排在下一次事件循环开始之前运行而不是必须要等到下一次开始之后才执行,这样可以提供一个更好的访问级别。

requestIdleCallback

允许浏览器告诉你的代码可以安全使用多少时间而不会导致系统延迟,从而有助于确保浏览器的事件循环平稳运行。如果你保持在给定的范围内,你可以使用户体验更好。

requestAnimationFrame

告诉浏览器——你希望执行一个回调函数,并且要求浏览器在下次重绘之前调用指定的回调函数。该方法需要传入一个回调函数作为参数,该回调函数会在浏览器下一次重绘之前执行。

参考