随着我们构建的网站越来越依赖 JavaScript,我们有时会以不易察觉的方式为我们发送的内容付出代价。在本文中,我们将介绍为什么如果您希望网站在移动设备上快速加载并可供用户互动,那么一点点自律会有所帮助。减少 JavaScript 的传输量意味着可以缩短网络传输时间、减少解压缩代码所花费的时间,以及缩短解析和编译此 JavaScript 的时间。
网络
大多数开发者在考虑 JavaScript 的成本时,都会考虑下载和执行成本。通过网络发送的 JavaScript 字节数越多,用户连接速度越慢,所需时间就越长。

这可能会导致问题,因为用户的有效网络连接类型可能实际上不是 3G、4G 或 Wi-Fi。您可能连接了咖啡店的 Wi-Fi,但连接的是 2G 速度的移动热点。
您可以通过以下方式降低 JavaScript 的网络传输费用:
- 仅发送用户需要的代码。
- 缩减大小
- 使用 UglifyJS 缩减 ES5 代码。
- 使用 babel-minify 或 uglify-es 缩减 ES2015+ 的大小。
- 压缩
- 移除未使用的代码。
- 使用开发者工具代码覆盖率确定可以移除或延迟加载的代码。
- 使用 babel-preset-env 和 browserlist,避免转译现代浏览器中已有的功能。高级开发者可能会发现,仔细分析其 webpack 软件包有助于确定减少不必要依赖项的机会。
- 如需剥离代码,请参阅摇树优化、Closure Compiler 的高级优化和库修剪插件,例如 lodash-babel-plugin 或 webpack 的 ContextReplacementPlugin(适用于 Moment.js 等库)。
- 缓存代码以最大限度地减少网络行程。
解析/编译
下载后,JavaScript 的最大开销之一是 JS 引擎解析/编译此代码所需的时间。在 Chrome 开发者工具中,解析和编译是“性能”面板中黄色“脚本”时间的一部分。

“自下而上”和“调用树”标签页会显示确切的解析/编译时间:
但为什么这很重要?

花费很长时间解析/编译代码可能会严重延迟用户与网站互动的时间。您发送的 JavaScript 越多,在网站可供互动之前解析和编译 JavaScript 所需的时间就越长。
对于浏览器而言,处理 JavaScript 的开销要高于处理大小相同的图片或网页字体 - Tom Dale
与 JavaScript 相比,处理大小相当的图片会产生大量费用(它们仍需解码!),但在普通移动硬件上,JS 更有可能对网页的可交互性产生负面影响。
当我们说解析和编译速度慢时,背景信息很重要,我们这里说的是普通手机。普通用户的手机可能配备速度较慢的 CPU 和 GPU,没有 L2/L3 缓存,甚至可能存在内存限制。
网络功能和设备功能并不总是匹配。即使拥有出色的光纤连接,用户也不一定拥有最好的 CPU 来解析和评估发送到其设备的 JavaScript。反之亦然,网络连接很差,但 CPU 速度极快。— Kristofer Baxter,LinkedIn
下面,我们可以看到在低端和高端硬件上解析约 1MB 解压缩(简单)JavaScript 的费用。在市场上运行速度最快的手机和普通手机之间,解析/编译代码所需的时间相差 2 到 5 倍。
如果是真实网站(例如 CNN.com),情况会怎样?
在高端 iPhone 8 上,解析/编译 CNN 的 JS 仅需约 4 秒,而在普通手机 (Moto G4) 上则需要约 13 秒。这可能会对用户与此网站完全互动的速度产生重大影响。
这凸显了在普通硬件(如 Moto G4)上进行测试的重要性,而不仅仅是在您可能随身携带的手机上进行测试。不过,情境很重要:请针对用户的设备和网络条件进行优化。
我们真的发送了太多 JavaScript 吗?呃,可能吧 :)
使用 HTTP Archive(前 50 万个网站)分析移动设备上的 JavaScript 状态,我们可以看到 50% 的网站需要超过 14 秒才能实现互动。这些网站仅解析和编译 JS 就要花费长达 4 秒的时间。

考虑到获取和处理 JS 及其他资源所需的时间,用户可能需要等待一段时间才能感觉页面已准备就绪,这或许并不令人意外。我们在这方面肯定可以做得更好。
从网页中移除不重要的 JavaScript 可以缩短传输时间、减少 CPU 密集型解析和编译以及潜在的内存开销。这也有助于让您的网页更快地实现互动。
执行时间
不仅解析和编译会产生开销,JavaScript 执行(在解析/编译后运行代码)是必须在主线程上执行的操作之一。执行时间过长还会推迟用户与网站互动的时间。

如果脚本执行时间超过 50 毫秒,则可交互时间会延迟下载、编译和执行 JS 所需的全部时间 - Alex Russell
为了解决此问题,JavaScript 可以拆分为小块,以避免锁定主线程。探索是否可以减少执行期间的工作量。
其他费用
JavaScript 还会通过其他方式影响网页性能:
- 记忆功能。由于 GC(垃圾回收),网页可能会频繁出现卡顿或暂停。当浏览器回收内存时,JS 执行会暂停,因此频繁收集垃圾的浏览器可能会比我们希望的更频繁地暂停执行。避免内存泄漏和频繁的 GC 暂停,以使网页保持流畅。
- 在运行时,长时间运行的 JavaScript 可能会阻塞主线程,导致页面无响应。将工作拆分为较小的部分(使用
requestAnimationFrame()或requestIdleCallback()进行调度)可以最大限度地减少响应速度问题,从而有助于改善 Interaction to Next Paint (INP)。
降低 JavaScript 传送成本的模式
当您尝试缩短 JavaScript 的解析/编译和网络传输时间时,有一些模式可以提供帮助,例如基于路由的分块或 PRPL。
PRPL
PRPL(推送、渲染、预缓存、延迟加载)是一种通过积极的代码拆分和缓存来优化互动性的模式:

我们来直观地了解一下它能产生的影响。
我们使用 V8 的运行时调用统计信息来分析热门移动网站和渐进式 Web 应用的加载时间。如我们所见,解析时间(以橙色显示)在许多网站的耗时中占了很大一部分:

Wego 是一个使用 PRPL 的网站,它设法缩短了其路由的解析时间,从而能够非常快速地达到可交互状态。上述许多其他网站都采用了代码拆分和性能预算,以尝试降低其 JS 费用。
渐进式引导
许多网站以牺牲互动性为代价来优化内容可见性。为了在 JavaScript 软件包较大时实现快速首次渲染,开发者有时会采用服务器端渲染,然后在最终提取 JavaScript 时对其进行“升级”以附加事件处理脚本。
请谨慎操作,因为这会产生费用。您 1) 通常会发送较大的 HTML 响应,这可能会影响互动性;2) 可能会让用户处于一种诡异的状态,即在 JavaScript 完成处理之前,有一半的体验实际上无法互动。
渐进式引导可能是更好的方法。发送一个功能最少的页面(仅由当前路由所需的 HTML/JS/CSS 组成)。 随着更多资源的到来,应用可以延迟加载并解锁更多功能。
根据视图中的内容加载相应比例的代码是终极目标。PRPL 和渐进式引导是可帮助实现此目标的模式。
总结
传输大小对于低端网络至关重要。解析时间对于 CPU 密集型设备非常重要。因此,保持较低的延迟时间至关重要。
团队已成功采用严格的性能预算,以缩短 JavaScript 传输和解析/编译时间。请参阅 Alex Russell 的“Can You Afford It?: Real-world Web Performance Budgets”(真实世界 Web 性能预算),了解有关移动设备预算的指导。
如果您要构建一个以移动设备为目标的网站,请尽最大努力在代表性硬件上进行开发,保持较低的 JavaScript 解析/编译时间,并采用性能预算来确保您的团队能够密切关注其 JavaScript 成本。
了解详情
- 2017 年 Chrome 开发者峰会 - 现代加载最佳实践
- JavaScript 启动性能
- 解决 Web 性能危机 - Nolan Lawson
- 您能负担得起吗?Real-world performance budgets — Alex Russell
- 评估 Web 框架和库 - Kristofer Baxter
- Cloudflare 的 Brotli 压缩实验结果(请注意,质量较高的动态 Brotli 可能会延迟初始网页呈现,因此请仔细评估。您可能需要改为静态压缩。)
- Performance Futures - Sam Saccone