在构建严重依赖 JavaScript 的网站时,我们有时会在不知不觉中为所发送的内容付费。在本文中,我们将介绍为什么如果您想让网站在移动设备上快速加载和交互,遵守一些规则会有所帮助。减少传递的 JavaScript 数量意味着网络传输所耗费的时间更少,解压缩代码所花费的时间更少,解析和编译此 JavaScript 的时间也更少。
网络
大多数开发者在考虑 JavaScript 的成本时,考虑的都是下载和执行成本。通过网络发送较多的 JavaScript 字节时,用户的连接速度越慢,花费的时间就越长。
这可能会是一个问题,因为用户的有效网络连接类型实际上可能不是 3G、4G 或 Wi-Fi。您可以使用咖啡店的 Wi-Fi 网络,但连接到 2G 速度的移动网络热点。
您可以通过以下方式降低 JavaScript 的网络传输费用:
- 仅发送用户所需的代码。
- 缩减大小
- 使用 UglifyJS 缩减 ES5 代码。
- 使用 babel-minify 或 uglify-es 缩减 ES2015+。
- 压缩
- 移除未使用的代码。
- 利用 DevTools 代码覆盖率识别可以移除或延迟加载的代码的机会。
- 请使用 babel-preset-env 和 browserlist,避免转译现代浏览器中已有的功能。高级开发者可能会发现仔细分析其 webpack 软件包有助于发现剪辑不需要的依赖项的机会。
- 如需了解如何剥离代码,请参阅 tree-shaking、Closure Compiler 的高级优化和库修剪插件(例如 lodash-babel-plugin)或 webpack 的 ContextReplacementPlugin(适用于 Moment.js 等库)。
- 缓存代码以最大限度地减少网络行程。
解析/编译
下载后,JS 引擎解析/编译此代码的时间是 JavaScript 最大的开销之一。在 Chrome 开发者工具中,解析和编译是“性能”面板中黄色“脚本”时间的一部分。
Bottom-Up 和 Call Tree 选项卡显示确切的解析/编译时间:
但是,为什么这很重要?
花费很长时间来解析/编译代码会严重延迟用户与网站互动的时间。发送的 JavaScript 越多,在网站进入可交互状态之前解析和编译这段代码所需的时间就越长。
就字节而言,浏览器处理 JavaScript 的成本高于同等大小的图片或网页字体 - Tom Dale
与 JavaScript 相比,处理相同大小的图片涉及很多成本(仍然需要解码!),但在普通的移动硬件上,JS 更有可能对页面的交互性产生负面影响。
我们提到解析和编译速度很慢;上下文很重要,在这里我们讨论的是普通手机。普通用户使用的可能是 CPU 和 GPU 速度缓慢、没有 L2/L3 缓存,甚至内存可能受限的手机。
网络功能和设备功能并不总是完全一致。拥有出色 Fiber 连接的用户并不一定拥有最佳 CPU 来解析和评估发送到其设备的 JavaScript。反之亦然 网络连接状况不佳,但 CPU 速度极快。— Kristofer Baxter,LinkedIn
我们可以看到,在低端和高端硬件上解析大约 1MB 解压缩(简单)JavaScript 的成本。对于市场上速度最快的手机与普通手机,解析/编译代码所用的时间相差 2-5 倍。
像 CNN.com 这样的真实网站怎么样?
在高端 iPhone 8 上,只需大约 4 秒即可解析/编译 CNN 的 JS,而普通手机 (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,因此频繁收集垃圾的浏览器会导致暂停执行的频率超出我们的预期。请避免内存泄漏和频繁的 GCS 暂停,以确保页面不会卡顿。
- 在运行时,长时间运行的 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?: 真实的网络性能预算”一文,以获得有关移动设备预算的指导。
如果您要构建一个以移动设备为目标平台的网站,请尽可能在具有代表性的硬件上进行开发,保持较短的 JavaScript 解析/编译时间,并采用性能预算,以确保您的团队能够密切关注其 JavaScript 费用。
了解详情
- 2017 年 Chrome 开发者峰会 - 现代加载最佳实践
- JavaScript 启动性能
- 解决 Web 性能危机 - Nolan Lawson
- 您能负担得起吗?实际性能预算 - Alex Russell
- 评估 Web 框架和库 - Kristofer Baxter
- Cloudflare 的 Brotli 压缩实验结果(请注意,质量更高的动态 Brotli 会延迟初始网页呈现,因此请仔细评估)。您可能想要改为静态压缩。)
- 性能期货 - Sam Saccone