

注:现代前端开发中性能瓶颈依然在于JavaScript,而且前端项目中JavaScript开销越来越大。本文详细介绍了 JavaScript 开销及原因,更是给出了相应的前端性能优化工具和方法。文章有点长,但是相信你可以获得很多有用的信息。
相关文章:
– [前端性能优化]移动端页面的 JavaScript 开销
– JavaScript性能优化技巧
构建交互式网站必定涉及向用户发送大量的JavaScript代码。 你在移动设备上打开页面,是否看起来像是一直加载,或尝试滚动时,页面没有任何反应?
因为一个字节一个字节的发送和解析,JavaScript仍然是我们向手机发送的最昂贵的资源,因为它可以在很大程度上延迟可交互性。
概括
- 要页面的快速运行,只需加载当前页面所需的 JavaScript 。 优先考虑用户需要的内容,并通过代码拆分来延迟加载其余部分。 这为您提供了加载和快速可交互的最佳机会。 一般情况下,基于路由堆栈的代码拆分是一个很好的选择。
- 拥抱性能预算并融入日常开发。 对于移动设备,目标是 JS预算压缩后<170KB。 未压缩的这仍然是 ~0.7MB 的代码。 预算对于成功是至关重要的,然而,他们不能神奇地孤立地修复性能。团队文化、结构和执行都很重要。没有预算的构建会导致性能倒退和失败。
- 了解如何 审查 并修剪你的 JavaScript 包。 当你 只需要库的某些小部分时,很有可能你会发送完整的库 ,而这个库的大多数代码用于浏览器的 polyfill 或 重复的代码。
- 每次互动都是新的“Time-to-Interactive”的开始; 在这种情况下考虑优化。传输大小对于低端移动网络至关重要,而设备的CPU 对于 JavaScript 解析时间至关重要。
- 如果客户端 JavaScript 没有提升用户体验,你需要问问自己是否真的有必要这么做。也许服务器端渲染的 HTML 会更快。考虑只在绝对需要客户端渲染的页面上才客户端框架。如果做得不好,服务器渲染和客户端渲染都会成为一场灾难。
WEB 因用户“体验”而膨胀
当用户访问您的站点时,您可能会发送大量文件,其中许多是脚本。 从Web浏览器的角度来看,这看起来有点像这样:
目前的中间网页传输压缩后的 JavaScript 资源约为 350KB 。未压缩的情况下,浏览器需要处理的脚本超过1MB。
注意:如果你不确定你的 JavaScript 包是否会延迟用户与你网站的可交互速度?可以通过 Lighthouse 。
其中一个重要因素是在移动网络上下载的代码在移动设备的CPU上处理需要多长时间。
我们来看看移动网络。
对于中间网站来说,350 KB的脚本不仅需要一段时间才能下载,现实情况是,如果我们查看热门网站,它们实际上会发送比这更多的脚本:
JavaScript 的成本所在
今天的网站通常会在他们的JS包中发送以下内容:
- 客户端的框架或者 UI 库;
- 状态管理解决方案(例如 Redux);
- Polyfills(通常被用于不需要它们的现代浏览器);
- 完整的库 VS. 仅需要的内容(例如,所有lodash,Moment +语言环境)
- 一套 UI 组件(按钮,标题,侧边栏等)
这些代码加起来越多,页面加载所需的时间就越长。
加载网页主要取决于三个因素。
分别是:加载是否被触发?加载的内容是否是有用?以及加载的内容是否可用?
加载的内容是否是有用? 是您绘制文本或内容的时刻,允许用户从中获取有价值的信息并与之交互。
然后,加载的内容是否可用? 是当用户可以开始与网页进行有意义地交互并发生某些事情。
我之前提到这个术语“交互”,意味着什么呢?
无论用户点击链接还是滚动页面,他们都需要看到为了响应他们的行为而实际发生的事情。无法实现这一目标的体验会让您的用户感到沮丧。
当浏览器运行您可能需要的许多事件时,它可能会在处理用户输入的同一线程上执行此操作。该线程称为主线程。
将过多的JavaScript加载到主线程(通过 <script> 等)是个问题。将JS拉入 Web Worker 或通过 Service Worker 进行缓存不会产生相同的负面等待交互(Time-to-Interactive)影响。
我们看到与我们合作的团队在许多类型的网站上遭受 JavaScript 影响交互性的情况。
以上是Google搜索中的一些示例,您可以从一开始使就使用这些UI,但如果某个网站运行过多的JavaScript,则可能会在实际发生某些事情之前出现延迟。 这会让用户感到有点沮丧。 理想情况下,我们希望所有交互尽快响应。
谈到 Time to Interactive,我们认为您的基准应该是在速度比较慢的3G连接的中端移动设备上,以五秒钟的速度可进行交互。 “但是,我的用户都在使用快速网络和高端手机!”……是吗? 你可能会使用“快速”的咖啡店WiFi,但实际上只能获得2G或3G的速度。 变化很大。
谁减少了 JavaScript 并减少了他们的可交互时间?
I’m really proud of the fact that Pinterest’s PWA loads in under 200kb. Twitter Lite is ~450kb. #javascript #pwa
— Zack Argyle (@ZackArgyle) 2018年6月30日
- Pinterest 将 JavaScript bundle 从 2.5MB 降低到小于 200KB,而 Time-to-Interactive 时间则从23秒降到5.6s. 收入增长44%,注册增长753%,移动互联网周活跃用户增长103%。
- AutoTrader 将 JavaScript bundle 大小降低了 56% 并将达到 Time-to-Interactive 的时长缩短了约50%。。
- Nikkei 将 JavaScript bundle 大小降低了 43% 并将 Time-to-Interactive 耗时缩短了14秒。
让我们设计一个更具弹性的移动网络,不依赖于大型JavaScript负载。
交互性受到很多事情的影响。比如用户通过咖啡店WiFi或旅途中的间歇性网络连接来加载网页。
当发生这种情况时,您需要处理大量JavaScript,这个时候用户需要等待页面呈现内容。 或者,如果某些东西已经呈现,但他们仍然需要等待很长时间才能与这些元素进行交互。 理想情况下,减少传输JavaScript可以缓解这些问题。
为什么JavaScript 开销如此高昂?
为了解释 JavaScript 为何有如此大的开销,我想向您介绍将内容发送到浏览器时发生的事情。 用户在浏览器的地址栏中键入URL:
请求被发送到服务器,然后服务器返回一些标记。然后,浏览器会解析这些标记并发现必要的CSS,JavaScript和图像。然后,浏览器必须获取并处理所有这些资源。
上面的场景准确描述了Chrome在处理您发送的所有内容时所发生的情况(是的,它是一个巨大的表情符号工厂)。
这里面临的挑战之一是 JavaScript 最终成为瓶颈。理想情况下,我们希望能够快速绘制像素,然后让页面可交互。但如果 JavaScript 是一个瓶颈,你最终只能看到一些你无法与之交互的东西。
我们希望防止 JavaScript 成为现代体验的瓶颈。
作为一名开发者,要记住的一件事:如果我们想让 JavaScript “变快”,我们必须让下载、解析、编译和执行 JavaScript 的整个过程都变快。
这意味着我们不仅要保证快速的网络传输,还要保证快速的脚本处理能力。
如果您花费很长时间在JavaScript引擎中解析和编译脚本上,那么就会延迟用户与您网页交互的时间。
为了提供一些关于此的数据,这里是V8(Chrome 的 JavaScript 引擎)在处理包含脚本的页面时花费时间的细分统计图如下:
从Chrome 66开始,V8在后台线程上编译代码,将编译时间缩短了20%。 但是解析和编译仍然非常昂贵,并且很少看到大型脚本能在50ms内执行完成,即使是在线程外编译也是如此。
对于 JavaScript, 要记住的另一件事是所有字节并不是相等的。 200 KB的脚本和200 KB的图像具有不同的开销。
需要在屏幕上对JPEG图像进行解码,栅格化和绘制。 需要下载 JavaScript 包然后进行解析,编译和执行 – 并且引擎需要完成许多其他步骤。 请注意,这些成本并不完全相同。
他们开始重视 JavaScript 开销问题的一个重要的原因是移动端。
移动设备频谱
他们可能使用低端或中端的手机,这些设备之间的差异也可能非常明显;散热、高速缓存大小、CPU、GPU – 根据您使用的设备,您最终可能会遇到JavaScript等资源的完全不同的处理时间。 低端手机的用户甚至可能在美国。
随着时间的推移,Android 手机越来越便宜,而不是更快。 这些设备通常 CPU 较差,使用更小的 L2 / L3 高速缓存。如果你希望他们都拥有高端硬件,那么普通用户该怎么办?
让我们通过来自现实网站的脚本来查看这个问题的更实用版本。 这 CNN.com 的JS处理时间:
<
figcaption>通过 WebPageTest 查看 CNN.com 的 JavaScript处理时间(来源)
在iPhone 8(使用A11芯片)上处理CNN的JavaScript比在普通手机上花费少9秒。 这可以让可交互时间快9秒。
既然WebPageTest支持阿尔卡特1X(在美国销售约100美元的手机,在发布时售罄),我们还可以看看在中低端硬件上加载CNN的过程。 通过对比三类机型的 filmstrips 片段,能看出低端机型甚至都不能用简单的慢来形容了。
有些用户不会使用快速网络或拥有最新最好的手机,因此我们开始在真实手机和真实网络上进行测试至关重要。 可变性(Variability)是一个真正的问题。
“可变性是杀死用户体验的原因” – Ilya Grigorik。 快速设备实际上有时可能很慢。 快速网络可能很慢,可变性最终会使一切变得缓慢。
当差异可能会破坏用户体验时,使用缓慢的基线进行开发可确保每个人(快速和慢速设置)都能获益。 如果您的团队可以查看他们的分析并准确了解您的用户实际访问您网站的设备,这些将会提示您应该在办公室中使用哪些设备来测试您的网站。
这里设置了许多配置文件,您可以使用这些配置文件已预先配置了常用设备。 例如,我们有一些中端移动设备准备测试,如 Moto G4 。
在代表性网络上进行测试也很重要。 虽然我已经谈到了低端手机和中端手机的重要性,但 Brian Holt 提出了这个观点:了解你的用户也非常重要。
如果您希望JavaScript快速,请注意低端网络的下载时间。 您可以进行的改进包括:减少代码,缩小源代码,利用压缩(即 gzip,Brotli,和 Zopfli)。
利用缓存重复访问。对于CPU速度慢的手机,解析时间至关重要。
如果您是后端或全栈开发人员,您应该知道您可以获得有关CPU,磁盘和网络的开销。
当我们构建的网站越来越依赖JavaScript时,我们有时会以一种我们不太容易看到的方式为我们发送的内容付出代价。
如何发送更少的JavaScript
最直接的方式是我们发送最少的脚本给用户,同时仍然给他们一个有用的网页。代码拆分 是最有效的选择。
代码拆分可以在页面级别、路由级别或组件级别完成。很多现代库和框架都通过 Webpack (中文)和 Parcel(中文)等打包工具来实现代码拆分。React (中文)、Vue.js和Angular 都提供了提供了这方面的指南。
// 拆分前 import OtherComponent from './OtherComponent'; const MyComponent = () => ( <OtherComponent/> );
// 拆分后 import Loadable from 'react-loadable'; const LoadableOtherComponent = Loadable({ loader: () => import('./OtherComponent'), loading: () => <div>Loading...</div>, }); const MyComponent = () => ( <LoadableOtherComponent/> );
为了尽可能快地重写他们的移动网络页面,以确保用户能够与他们的网站进行交互,Twitter 和 Tinder 在采用激进的代码拆分,他们的可交互时间( Time to Interactive )提高了50%。。
像 Gatsby.js(React),Preact CLI 和 PWA Starter Kit 这样的堆栈尝试强制执行良好的默认设置,以便在中端移动硬件上快速加载和获取交互。
许多这些网站所做的另一件事是:将审计分析 JavaScript 作为其工作流程的一部分。
我们可以使用Webpack Bundle Analyzer、Source Map Explorer 和 Bundle Buddy 等工具来分析JavaScript包,寻找进行代码拆分的可能性。
这些工具可以对 JavaScript 包的内容进行可视化:它们突出显示大型的库、重复的代码和你可能不需要的依赖项。
如果您使用webpack,您可能会发现我们在 这个仓库中提到的公共库 会很有用。
度量、优化、监控和重复
如果您不确定一般的JavaScript性能是否有任何问题,请查看 Lighthouse:
最近 LightHouse 添加了一个功能,即标记出高 “JavaScript boot-up time(JavaScript 启动时间)” 的支持。这个审计分析突出显示了可能需要花费很长时间进行解析/编译的脚本,这会延迟可交互性。您可以并据此拆分和优化你的代码。
您可以做的另一件事是确保您没有将未使用的代码发送给您的用户:
My 3 Steps for any project:
1. Load page on Chrome, use Coverage Tool (DevTools => Ctrl+Shift+P => type "Coverage" => press Drawer: Coverage),2. Press reload icon and see how much unused code you are shipping init. page load.
3. Find those modules and lazy load with import()
— Sean (肖恩) Larkin (@TheLarkInn) 2018年6月28日
提示:通过覆盖记录,您可以与您的应用进行交互,DevTools将更新您使用的包的数量。
这对于找出可以进行拆分的脚本以及延迟加载非关键脚本来说非常有用。
如果你正在寻找一种为用户提供高效的 JavaScript 分发模式,请查看 PRPL模式。
PRPL是高性能的加载模式。它代表:
- 推送(P)ush – 为初始网址路由推送关键资源。
- 渲染(R)ender – 渲染初始路由。
- 预缓存(P)re-cache – 预缓存剩余路由。
- 延迟加载(L)azy-load – 延迟加载并按需创建剩余路由。
PRPL(Push、Render、Precache和Lazy-Load)是一种模式,用于对每个路由进行代码拆分,然后利用 Service Worker 预先缓存未来路由需要用到的JavaScript和逻辑,并按需延迟加载。
这意味着当用户导航到应用中的其他视图时,它很可能已经存在于浏览器缓存中,因此他们在启动脚本和获取可交互性方面的成本降低了很多。
如果您关心性能,或者您曾经为您的网站做过性能补丁,那么你就会知道,有时候您可能最终要解决某个问题,但几周后又出现了,发现团队中有人正在开发某个功能,无意中破坏了应用。就像这样:
值得庆幸的是,我们可以尝试解决这个问题,一种方法是制定 性能预算 。
性能预算是至关重要的,因为它们使每个人保持一致。它们创造了一种共享热情的文化,以不断改善用户体验和团队责任。
预算定义了可衡量的约束,让团队去实现性能目标。因为你不能突破预算的限制,所以每走一步都要考虑好性能,而不是事后再来解决。
根据 Tim Kadlec 的经验,性能预算的指标 可包括:
- 里程碑时间 – 基于加载页面的用户体验的时间(例如,可交互时间( Time to Interactive ))。 您常常希望对几个里程碑时间进行配对,以便在页面加载期间准确地表示完整的内容。
- 基于质量的指标 – 基于原始值(例如JavaScript的权重,HTTP请求的数量)。 这些都专注于浏览器体验。
- 基于规则的指标 – 由 Lighthouse 或 WebPageTest 等工具生成的分数。通常,一个数字或系列数字来对你的网站进行评分。
Most teams need simple, actionable advice. And budgeting for TTI (and by proxy, the JS payload) hits the critical-path issue on the head. I dig into it here:https://t.co/wVAuo5gwld
— Alex Russell (@slightlylate) 2018年7月15日
Alex Russell 发布了关于性能预算的推特,其中有几点值得注意:
- “领导阶层的支持很重要。为了保持良好的整体用户体验,而暂停搁置功能性开发工作,通常这是对技术产品经过深思熟虑后的意愿。“
- “性能是由工具支持的文化。浏览器会自动尽可能优化HTML+CSS。将更多的工作转移到会给团队和工具带来负担的JS中。“
- “性能预算不会让你伤心。他们的存在是为了让组织自我纠正。团队需要性能预算来限制决策空间并帮助实现它们。“
每个参与人和其执行力都会关系到网站的用户体验。
建议前端工程师在计划会议和其他会议中讨论性能。向业务利益相关者询问他们的业绩预期,他们是否了解前端性能如何影响他们关心的业务指标?询问团队如何计划解决性能瓶颈问题。
虽然这里的答案可能不尽如人意,但至少对话开始了。
- 创建您的性能愿景。 这是一份关于业务利益相关者和开发人员认为“良好表现”的协议
- 设定性能预算。 从愿景中提取关键绩效指标(KPI),并从中设定切合实际的可衡量目标。 例如 “在5s内加载并可进行交互”。 规模预算可能会失败。 例如“JS压缩后保持 < 170KB”
- 创建关于KPI的定期报告。 这可以是发送给业务的定期报告,突出显示进度和成功部分。
Andy Still的Web Performance Warrior和 Lara Hogan的Designing for Performance 都是优秀的书籍,讨论如何考虑如何实现性能文化。
那么性能预算的工具有哪些呢? 您可以设置 Lighthouse 评分预算与 Lighthouse CI 项目持续集成:
请求被合并。Lighthouse Thresholds是另一种基于配置的方法来设置预算。
寻找进一步的参考? U.S Digital Service 通过设定时间到交互等指标的目标和预算,记录他们用 Lighthouse 跟踪的性能的方法 。
接下来……
每个站点都应该可以访问 实验和真实场景中性能相关的数据 。
要跟踪 JavaScript 可能对 RUM(真实用户监控)设置中的用户体验产生的影响,网络上有两件事情,我建议大家去看看。
第二个是 First Input Delay(首次输入延迟) ,是用于度量从用户首次向网站发起交互(如当他们点击按钮时)到浏览器能够响应该交互的时间。FID是一个早期指标,不过现在已经有一个可用的 polyfill,你可以尝试下。
在这两者之间,您应该能够从真实用户那里获得足够的遥测数据,以查看他们遇到的JavaScript性能问题。
我们看到了几个常见的漏洞,包括在文档头部使用JavaScript来决定向用户显示那个版本的A/B测试。或者将A/B测试所有的JS都发送给用户,但实际上只用了其中一个 。
Blocking js in the head to make a front-end A/B decision is one of the worst things you can do for performance. Came up in the context of common perf issues I see on sites
— Patrick Meenan (@patmeenan) 2017年11月30日
如果这是你目前遇到的主要瓶颈,我们还提供了一个有关加载第三方JavaScript的指南。
总结
性能优化是一段长途旅行。许多小的变化可以带来巨大的收益。
使用户能够以最少的阻力与您的站点进行交互。 运行最少量的JavaScript以提供真正的价值。 这可能意味着你需要采取渐进的步骤来实现目标。
最后,您的用户会感谢您。
参考
- Can You Afford It?: Real-world Web Performance Budgets
- Progressive Performance
- Reducing JavaScript payloads with Tree-shaking
- Ouch, your JavaScript hurts!
- Fast & Resilient?—?Why carving out the “fast” path isn’t enough
- Web performance optimization with Webpack
- JavaScript Start-up Optimization
- The Impact Of Page Weight On Load Time
- Beyond The Bubble?—?Real-world Performance
- How To Think About Speed Tools
- Thinking PRPL
原文链接:https://medium.com/@addyosmani/the-cost-of-javascript-in-2018-7d8950fbb5d4
如果能把页面美化一下,估计会比掘金的访问量差不多