日常中的 JavaScript 代码会经常触发视觉变化。有时会直接通过样式操作,有时是计算导致视觉变化,例如搜索或排列数据。不合时宜或长时间运行的 JavaScript 是性能问题的常见原因。 不仅仅是 JavsScript ,任何语言的性能分析都是一门艺术,我们所编写的 JavsScript 与实际运行的代码完全不同。
现代浏览器使用 JIT 【Just-In-Time(即时编译)】编译器、解释器和各种优化和技巧来尝试为我们提供尽可能快的执行速度,从而极大地改变代码的动态。(解释器可以快速地执行 JavaScript 代码,但它的执行速度通常比较慢。而 JIT 编译器可以将 JavaScript 代码编译成本地机器代码,从而提高代码的执行速度。)
🌈Tip: 在 V8 引擎中,当 JavaScript 代码被执行时,它首先会被解释器解释执行。然后,如果某个函数被多次调用,V8 引擎会使用 JIT 编译器将该函数的代码编译成本地机器代码,并将其缓存起来,以便以后的调用可以直接执行编译后的代码,从而提高执行速度。
总结:
- 避免 setTimeout 或 setInterval 进行视觉更新,通过使用 requestAnimationFrame 进行代替;(关于定时器和 requestAnimationFrame 更多信息见:什么是 Js 事件环篇)
- 尝试将长时间运行在 JavaScript 上的工作从主线程移到 Web Workers 见:什么是 Js
- 使用微任务在多个帧上进行 DOM 更改;
- 通过 Chrome DevTools 的 Timeline 和 JavaScript Profiler 来评估 JavaScript 对整个程序的影响。
使用 requestAnimationFrame 来改变视觉效果
当屏幕上发生视觉变化时,我们希望在正确的时间为浏览器完成工作。so,我们可以通过调用 requestAnimationFrame 来使得浏览器在下次重绘之前更新动画。
function cb(time) {
//
}
requestAnimationFrame(cb)
框架或示例可以使用 setTimeout 或 setInterval 来执行动画等视觉更改,但这样做的问题是回调将在帧中的某个点运行,可能就在最后,这通常会导致我们错过一帧,从而导致卡顿。
requestAnimationFrame 在构建时考虑了另一个好处:让浏览器选择最佳帧间隔允许在非活动选项卡中有很长的间隔。用户可以玩CPU密集型游戏,然后打开一个新选项卡或最小化窗口,游戏将暂停,留下可用于其他任务的资源。
降低复杂度或者使用 Web Workers
JavaScript 的 “frame tax”
JavaScript 的 “frame tax” 是指在 JavaScript 引擎中执行函数时所需的额外开销或成本,这些开销通常与函数调用的帧(frame)有关。在计算机科学中,帧(frame)是指存储函数调用的参数、局部变量、返回地址等信息的一种数据结构。
在 JavaScript 中,每次函数调用都需要创建一个帧来存储函数的参数、局部变量等信息。这个帧需要占用一定的内存空间,并且在函数返回时需要被销毁。因此,在 JavaScript 引擎中执行函数时,每次函数调用都会带来一定的额外开销,这个开销就是 “frame tax”。
“frame tax” 可能会对 JavaScript 的性能产生影响,特别是在执行大量短小的函数调用时。因此,为了尽可能地减少 “frame tax” ,可以使用一些优化技术,例如内联函数、使用尾调用优化、避免不必要的函数调用等。这些技术可以减少函数调用的次数,从而降低“frame tax”带来的性能影响。
“frame tax” 并不是 JavaScript 所独有的,它在其他编程语言中也存在。不同的语言和引擎对于函数调用的实现方式和优化技术也有所不同,因此具体的 “frame tax” 大小和影响因素也会有所不同。
我们可以通过 Chrome DevTools 的 Performance 面板来衡量应用的成本:
Main 部分提供了应用在运行时的火焰图,由此可以细致到分析是哪些因素在影响页面的性能,从而查找修复执行时间过长的任何因素。
另一种方式是通过 FPS 仪表工具,它在页面运行时提供页面 FPS 的实时运行状态:
避免不必要的微优化
我们对某一个工具会有更快的构建版本总是充满“好奇”,例如先前的 Vite 热。
例如:请求元素的 offsetTop 比计算 getBoundingClientRect() 更快,且结果的计算上也是正确的,但是每帧仅仅只调用这样的函数的情况很少,所以专注于 JavaScript 性能的这一方面往往是浪费精力的。最终结果可能是只节省了几 ms。
如果我们的应用程序是一个计算成本很高,那么的确需要做到尽可能的优化,因为在运行上需要把大量的计算放在每一帧上,以保证效果反馈的流畅。
简而言之就是,保证每一个优化的必要性。
补充
JIT:在 V8 引擎中,当 JavaScript 代码被执行时,它首先会被解释器解释执行。然后,如果某个函数被多次调用,V8 引擎会使用 JIT 编译器将该函数的代码编译成本地机器代码,并将其缓存起来,以便以后的调用可以直接执行编译后的代码,从而提高执行速度。