优化 JavaScript 执行

JavaScript 通常会触发视觉变化。有时是直接通过样式操作,有时是产生视觉变化的计算,例如搜索数据或对数据进行排序。时机不当或长时间运行的 JavaScript 可能是导致性能问题的常见原因。您应该设法尽可能减少其影响。

保罗·刘易斯
Paul Lewis

JavaScript 通常会触发视觉变化。有时是直接通过样式操作,有时是会产生视觉变化的计算,例如搜索数据或对数据进行排序。时机不当或长时间运行的 JavaScript 可能是导致性能问题的常见原因。您应该设法尽可能减少其影响。

JavaScript 性能分析是一门艺术,因为您编写的 JavaScript 与实际执行的代码并不相似。现代浏览器使用 JIT 编译器以及各种优化和技巧来尝试让您以最快的速度执行,而这将显著改变代码的动态。

尽管如此,您绝对可以采取一些措施来帮助您的应用很好地执行 JavaScript。

摘要

  • 避免使用 setTimeout 或 setInterval 实现视觉更新;请始终使用 requestAnimationFrame。
  • 将长时间运行的 JavaScript 从主线程移至 Web Worker。
  • 使用微任务对多个帧进行 DOM 更改。
  • 使用 Chrome DevTools 的 Timeline 和 JavaScript 性能分析器来评估 JavaScript 的影响。

使用 requestAnimationFrame 实现视觉变化

当屏幕上发生视觉变化时,您需要在合适的时间(即帧的开头)执行您的工作。保证 JavaScript 在帧开始时运行的唯一方法是使用 requestAnimationFrame

/**
    * If run as a requestAnimationFrame callback, this
    * will be run at the start of the frame.
    */
function updateScreen(time) {
    // Make visual updates here.
}

requestAnimationFrame(updateScreen);

框架或示例可以使用 setTimeoutsetInterval 来执行动画等视觉更改,但问题在于回调将在帧中的某个点运行,可能是在结束时,这往往会导致我们错过某个帧,进而导致卡顿。

setTimeout 导致浏览器错过帧。

实际上,jQuery 过去将 setTimeout 用于其 animate 行为。版本 3 中已更改为使用 requestAnimationFrame。如果您使用的是旧版 jQuery,可以对其进行修补以使用 requestAnimationFrame(强烈建议)。

降低复杂性或使用 Web Worker

JavaScript 在浏览器的主线程上运行,与样式计算、布局以及绘制(在许多情况下)一起运行。如果您的 JavaScript 运行了很长时间,则会阻塞其他这些任务,可能导致丢帧。

因此,您应该对 JavaScript 何时运行以及运行多长时间保持好策略。例如,对于滚动之类的动画,最好是希望将 JavaScript 保持在 3-4 毫秒的范围内。如果超过这个时间,则可能会耗费太多时间。如果您处于空闲期,可以对所花费的时间更加放松。

在许多情况下,您可以将纯计算工作转移到 Web Worker,例如在不需要 DOM 访问权限的情况下处理此类工作。数据操作或遍历(例如排序或搜索)通常非常适合这种模型,加载和模型生成也是如此。

var dataSortWorker = new Worker("sort-worker.js");
dataSortWorker.postMesssage(dataToSort);

// The main thread is now free to continue working on other things...

dataSortWorker.addEventListener('message', function(evt) {
    var sortedData = evt.data;
    // Update data on screen...
});

并非所有工作都适合此模式:Web Worker 没有 DOM 访问权限。如果您的工作必须在主线程上执行,请考虑使用批处理方法,将较大的任务划分为多个微任务,每个任务花费的时间不超过几毫秒,并跨每一帧在 requestAnimationFrame 处理程序中运行。

这种方法会影响用户体验和界面,并且您需要使用进度或 activity 指示器来确保用户知道任务正在处理中。在任何情况下,此方法都将使应用的主线程保持空闲状态,从而帮助其对用户互动保持响应。

了解 JavaScript 的“帧税”

在评估框架、库或您自己的代码时,请务必逐帧评估运行 JavaScript 代码所需的费用。在执行对性能至关重要的动画工作(如过渡或滚动)时,这一点尤为重要。

Chrome 开发者工具的“性能”面板是衡量 JavaScript 费用的最佳方式。您通常会获得如下低层级记录:

Chrome 开发者工具中的性能记录

Main 部分提供了一个 JavaScript 调用的火焰图,以便您准确分析调用了哪些函数以及每个函数花费的时间。

掌握这些信息后,您可以评估 JavaScript 对应用性能的影响,并开始找出并修复函数执行时间过长的热点。如前所述,您应设法移除长时间运行的 JavaScript,或者如果无法移除,则应将其移至 Web Worker,从而释放主线程以继续其他任务。

请参阅分析运行时性能入门,了解如何使用“性能”面板。

避免微优化 JavaScript

浏览器执行某个版本的一个版本的速度比另一个版本快 100 倍可能很酷,比如请求元素的 offsetTop 比计算 getBoundingClientRect() 快,但几乎始终是,您在每一帧中调用此类函数的次数是很少的,因此关注 JavaScript 性能的这个方面通常都是白费力的。通常您只节省零点几毫秒的时间。

如果您要开发游戏或计算成本高昂的应用,则可能属于本指南的例外情况,因为您通常需要将大量计算放入单个帧中,在这种情况下,一切都有所帮助。

简而言之,您应该非常谨慎地进行微优化,因为它们通常不会映射到您正在构建的应用类型。