优化 JavaScript 执行

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

Paul Lewis

JavaScript 通常会触发视觉更改。有时,是直接通过样式操作实现的,有时则是通过计算(例如搜索或排序数据)实现的。时间安排不当或长时间运行的 JavaScript 是导致性能问题的常见原因。您应尽可能减少其影响。

JavaScript 性能分析可以说是一门艺术,因为您编写的 JavaScript 与实际执行的代码完全不同。现代浏览器使用 JIT 编译器以及各种优化和技巧,力求尽可能快速执行,这会显著改变代码的动态性。

尽管如此,您还是可以采取一些措施来帮助应用顺利执行 JavaScript。

摘要

  • 避免使用 setTimeout 或 setInterval 进行视觉更新;请始终改用 requestAnimationFrame。
  • 将长时间运行的 JavaScript 从主线程移至 Web Worker。
  • 使用微任务在多个帧中进行 DOM 更改。
  • 使用 Chrome 开发者工具的时间轴和 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 毫秒左右。超过这个时间,就可能会占用太多时间。如果您处于空闲期,则可以对所需时间放宽要求。

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

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 处理程序内运行。

这种方法会对用户体验和界面产生影响,您需要确保用户知道系统正在处理任务,方法是使用进度或活动指示器。无论在何种情况下,这种方法都会使应用的主线程保持空闲状态,有助于应用对用户互动保持响应。

了解 JavaScript 的“帧税”

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

若要衡量 JavaScript 的开销,Chrome 开发者工具的“性能”面板是最佳方式。通常,您会收到如下低级记录:

Chrome DevTools 中的性能记录

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

有了这些信息,您就可以评估 JavaScript 对应用性能的影响,并开始查找并修复函数执行时间过长的所有热点。如前所述,您应尝试移除长时间运行的 JavaScript,如果无法移除,则将其移至 Web Worker,以释放主线程来继续执行其他任务。

如需了解如何使用“性能”面板,请参阅开始分析运行时性能

避免对 JavaScript 进行微优化

得知浏览器可以比另一个版本更快地执行某个操作(例如,请求元素的 offsetTop 比计算 getBoundingClientRect() 更快)可能很酷,但几乎总是如此,您每帧只会调用这些函数几次,因此通常没必要专注于 JavaScript 性能的这一方面。通常,您只会节省几毫秒的时间。

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

简而言之,您应非常谨慎地使用微优化,因为它们通常与您构建的应用类型不符。