当浏览器运行 JavaScript 时,它会将脚本组织成按顺序运行的任务,例如进行 fetch 请求、通过事件处理程序驱动用户交互和输入、运行 JavaScript 驱动的动画等等。
大部分任务都在主线程上运行,其中包括运行在 Web Worker 中的 JavaScript。主线程一次只能运行一个任务。
当单个任务的执行时间超过 50 毫秒时,它被归类为长任务。如果用户在长任务正在运行时尝试与页面交互或请求重要的 UI 更新,他们的体验将受到影响。预期的响应或视觉更新将被延迟,导致 UI 看起来迟钝或无响应。
为了解决这个问题,你需要将长任务分解为较小的任务。这样可以给浏览器更多机会执行重要的用户交互处理或 UI 渲染更新,浏览器可以在每个较小任务之间执行它们,而不是仅仅在长任务之前或之后执行。在你的 JavaScript 中,你可以通过将代码拆分为单独的函数来实现这一点。这样做也有其他几个原因,比如更容易维护、调试和编写测试。
例如:
然而,这种结构对于主线程阻塞并没有帮助。由于所有五个函数都在一个主函数中运行,浏览器将它们整体作为一个长任务运行。
为了处理这个问题,我们倾向于定期运行一个“yield”函数,以使代码“让步给主线程”。这意味着我们的代码被分成多个任务,在执行每个任务之间,浏览器有机会处理高优先级任务,比如更新 UI。一个常见的模式是使用 将执行推迟到一个单独的任务中:
可以在这样的任务运行模式中使用它,以在每个任务运行后让步给主线程:
为了进一步改进,我们可以使用 (en-US),仅在用户尝试与页面交互时运行 函数:
这样可以避免在用户积极与页面交互时阻塞主线程,从而提供更流畅的用户体验。然而,通过仅在必要时让步,我们可以在没有用户输入需要处理时继续运行当前任务。这也避免了任务被放置在队列末尾,排在其他非必要的浏览器初始化任务之后。