JavaScript 的代价(2018 版) 已翻译 100%

oschina 投递于 08/06 15:41 (共 18 段, 翻译完成于 08-17)
阅读 3140
收藏 40
5
加载中
关于作者: Addy Osmani, 英国。谷歌的经理,在 Chrome • Passionate 负责改善网页速度。

建立交互式网站包括向用户发送 JavaScript 。通常,太多了。你是否经历过在一个手机页面上,它看起来已经加载好了,但是点击一个链接或者试图滚动页面的时候,什么也没发生?

一字节又一字节,JavaScript 仍然是我们发送给手机的代价最大的资源,因为它会很大程度上延迟交互。

由 WebPageTest(src) 评测的 CNN.com 的 JavaScript 处理时间。高端手机(iPhone8)在约4s的时间处理脚本。相比较而言,普通手机(Moto G4)是约13s的时间,以及2018年低端手机(Alcatel 1X)是约36s。

现在我们讨论一些策略,可以让你高效地传送 JavaScript ,同时给用户提供一个有价值的体验。

概括:

  • 要保持快速,则只加载当前页面必要的 JavaScript 。优先考虑用户需要的内容,然后使用代码拆分延迟加载剩下来的内容。这是快速加载和交互的最好的机会。默认情况下,基于路由的代码拆分堆栈是一个转折。

  • 接受性能预算,学会在预算中生活。对于手机来说,JS的预算目标为简化/压缩后小于170KB。未压缩时代码约为0.7MB。预算对成功至关重要,然而,他们单独不能神奇地修正 perf 数值。团队文化,结构和强制措施。没有预算的项目建立会导致性能退化并导致失败。

  • 学习如何审计并裁剪 JavaScript 捆绑库。当你只需要一小部分却搭载了整个库,浏览器不需要的填充字符,或者重复代码,这些很容易发生。

  • 每个交互都是一个新的“交互时间”的开始;考虑在这种情况下进行优化。传送数据的大小对低端手机网络至关重要,而且 JavaScript 解析时间受设备 CPU 限制。

  • 如果客户端 JavaScript 对用户体验没有好处,问问自己是否真的有必要。也许服务端渲染 HTML 会更快一些。考虑将客户端框架限制到绝对需要它们页面上的使用。如果做的不好,服务器渲染和客户端渲染都会是灾难。

(本文基于我最近的“JavaScript 的代价”的演讲:https://youtu.be/63I-mEuSvGA

琪花亿草
琪花亿草
翻译于 08/08 11:56
0

网站因用户“体验”而膨胀

当用户访问网站,你可能正在下载大量文件,其中很多都是脚本。从给一个web浏览器的角度来看有点像这个:


扔给你一大堆文件

虽然我很喜欢JavaScript,但它总是网站中消耗最大的东西。我想解释一下为什么这是一个主要问题。

现在中等的web页面搭载了大概350KB的简化或压缩后的JavaScript脚本。浏览器需要处理的未压缩的脚本膨胀到了超过1MB。

注:不确定你的JavaScript包是否延迟了用户与网站交互速度?查看Lighthouse

2018年7月的HTTP压缩状态的JavaScript报告中的统计突出显示了中等web页面搭载了约350KB的简化或压缩后的脚本。这些页面要花15s才能交互。

在移动设备上,搭载这么多的JavaScript脚本从经验来看要花费超过14+秒才能加载并交互

其中的一个很大的因素是在移动网络中下载代码,然后再移动设备CPU上处理它,这个过程所花费的时间。

我们来看移动网络。

在某一指标上表现较好的国家,颜色较深。不包括在内的国家是灰色的。还有值得注意的是,即使在美国,农村的宽带速度要比城市慢20%。

这个来自OpenSignal的图表展示了全球4G网络的稳定性,以及每个国家的用户体验到的平均连接速度。正如我们看到的,很多国家的连接速度仍比我们想象中要慢。

不仅中型网站的350KB的脚本要花上一段时间才能下载,事实上,如果我们浏览热门网站,实际上会加载比这更多的脚本:


Facebook.com和其他网址相关数据”中的未压缩的JS包大小数据。像谷歌表格这样的网站被突出显示为最多加载5.8MB的脚本(在解压缩后)。

我们在桌面和移动web上都遇到了这个瓶颈,这些网站有时会加载几兆字节的代码,然后浏览器需要处理这些代码。问题是,你能负担得起这么多JavaScript脚本吗

琪花亿草
琪花亿草
翻译于 08/08 15:14
0

JavaScript 有代价

“含有这么多脚本的网站根本不能送达全球的诸多用户;统计表示,用户不会(以后也不会)等待它们加载” — Alex Russell

注:如果你使用了大量的脚本,应该考虑使用 code-splitting 对其进行分解,或者使用 tree-shaking 技术减少 JavaScript 的加载量

现代网站通常会通过 JS 包发送下面这些东西:

  • 客户端框架或 UI 库

  • 状态管理(比如 Redux)

  • Polyfills(一般现代浏览器不需要他们)

  • 完整的库或仅用到的部分(比如 lodash 完整库、Moment + 其本地库)

  • 一套 UI 组件(按钮、顶部、侧边栏等)

累积起来的代码越来越多,页面加载的时候也就越来越长。

加载网页就像电影胶片一样,有三个关键时刻。

即:是否发生?是否有用?是否可用?

加载是一个过程。我们正逐渐开始关心用户的良好体验。我们不再盯着 onload 和 domContentLoaded,而是会问“用户什么时候才能正常使用页面?”如果用户点击用户界面中L的o某个地方,是否有所反馈?

是否正在发生是指屏幕上开始显示某些内容。(导航开始了吗?服务器在响应吗?)

是否有用 指文本或内容显现之后,用户是否通过体验或参与感受到价值。

还有是否可用是指用户可以根据经验开始交互并发生一些事情。

我之前也提到过这个术语:“交互”,它到底是什么意思呢?

交互时间的可视化强调,不好的体验会让用户认为他们能达到某个目标,但实际上页面还没有加载完要达到这个目标所需要的代码。感谢 Kevin Schaaf 的关于交互的动画

对于要交互的页面,它必须能够快速响应用户输入。较小的 JavaScript 可以保证快速响应。

无论用户点击链接还是滚动页面,他们都需要看到有反馈他们动画的事情发生。如果做不到这样的体验,用户就会感沮丧。

灯塔有一系列以用户为中心的性能指标,比如在实验设置中的交互时间。

通常发生的地方是当服务端在渲染的过程中,下载一堆“溶入”界面的 JavaScript(添加事件处理函数和其它行为)。

浏览器可能会在处理用户输入的线程上运行许多可能需要处理的事件。这个线程称为主线程。

在主线程上加载太多 JavaScript(通过 <script> 等)会是个问题。把脚本加载到  Web Worker 或者由 Service Worker 处理脚本会减轻这些与交互时间相关的负责影响。

(这里有一个用户点击 UI 的例子。通常,用户勾选复选框,或者点击链接,一切都很美好。但如果我,们模拟阻塞主线程,就什么事都不会发生了。用户无法勾选复选框,也不能点击连接,因为主H线程被阻塞了:https://youtu.be/N5vFvJUBS28

应该尽可能地避免阻塞主线程。了解更多内容,请看 为什么 Web 开发者需要关心交互性

边城
边城
翻译于 08/10 22:33
0

我们看到我们合作的团队遭受JavaScript影响了许多类型网站的交互性。

JavaScript可以延迟可见元素的交互性。可视化是Google搜索中的一些UI元素

太多(主线程)JavaScript可以延迟可见元素的交互性。这对许多公司来说都是一个挑战。

以上是Google搜索中的一些示例,您可以在这些示例中开始使用UI,但如果某个网站运行过多的JavaScript,则可能会在实际发生某些事情之前出现延迟。这会让用户感到有点沮丧。理想情况下,我们希望所有体验尽快互动。

通过WebPageTest和Lighthouse衡量news.google.com的互动时间(来源)

通过衡量Google新闻在移动设备上的交互时间,我们观察到大约7s的高端交互与低端设备在55秒内实现交互的巨大差异。那么,什么是交互性的良好目标?

谈到Time to Interactive,我们认为您的基准应该是在中等移动设备上的慢速3G连接上以五秒钟的速度进行交互。“但是,我的用户都在使用快速网络和高端手机!”......是吗?你可能会使用“快速”的咖啡店WiFi,但实际上只能获得2G或3G的速度。多变性问题

硅谷课堂
硅谷课堂
翻译于 08/10 11:51
0

谁传输更少的 JavaScript 以减少响应时间?


我们来设计一个更具弹性的 Web,它不依赖于加载巨大的 JavaScript。

交互性会影响很多东西。它可能会影响网站的移动数据规划,或者咖啡厅的 WiFi,或者他们I只是伴随着时断时续的连接。

这些事情发生的时候,你有一大堆的 JavaScript 需要运行,用户可以放弃等待网站的渲染。另外,如果有东西在渲染,也需要等待大量的时间才可以交互。理想情况下,较少的 JavaScript 可以减轻这些问题。

边城
边城
翻译于 08/10 22:49
0

为什么 JavaScript 如此昂贵?

为了解释 JavaScript 会有如此之大的代价,我想告诉你在内容发送到浏览器之后发生了些什么。当用户在浏览器的地址栏输入 URL:

请求会被发送到服务器,然后服务器会返回一些标记。接着,浏览器解析这些标记,并找到必不的可少的 CSS、JavaScript 和图片。然后,浏览器还得获取并处理所有这些资源。

上面的动画准确描述了 Chrome 在处理你发送的所有内容时要干的事情(确实,它是一个巨大的表情工厂)。

这里存在一个挑战:JavaScript 最终会成为瓶颈。设想,我们希望能快速画出每一个像素,然后让页面可以交互。但是,如果 JavaScript 成为瓶颈,你最终只能看到东西却不能交互。

我们希望防止 JavaScript 为成现代体验的瓶颈。

要记住一点,作为一个流程,如果我们想要 JavaScript 变得更快,我们就必须快速地下载、解析、编译并执行它。

也就是说,我们必须保证快速的网络传输,以及处理关于脚本其它方面的事情。

边城
边城
翻译于 08/11 20:25
0

如果你花很长的时间在 JavaScript 引擎中解析和编译脚本,就是延迟用户与你的体验交互的时间。

为了提供这方面的数据,这里有一个 V8(Chrome 的 JavaScript 引擎)在处理页面中脚本的时间分解数据:

JavaScript 解析/编译 = 在页面加载时 10–30% 时间消耗在 V8 (Chrome 的 JS 引擎) 上

橙色代表了当下流行的网站消耗在解析 JavaScript 上的全部时间。黄色部分则是编译所消耗的时间。它们总计占用了处理页面上 JavaScript 30% 的时间 —— 这是真实的成本。

对于 Chrome66 来说,V8 在后台线程中编译代码,将编译时间减少了 20%。但是解析和编译的代价仍然很高,而且想要看到一个大型脚本执行时间少于 50ms,实属罕见,哪怕不在主线程编译。

另一个要知道的是 JavaScript 的所有字节都不等价。200KB 的脚本和 200KB 的图片所需要的代价差异很大。

并非所有字节都是等价的。除开原始的网络传输成本,200KB 的脚本和 200KB 的 JPG 所需要的代价大相径庭。

他们的下载时间可能一样,但处理起来却需要不同的成本。

JPEG 图像需要解码、光栅化,然后绘制在屏幕上。JavaScript 需要下载,然后解析、编译、执行—— 还有大量需要引擎完成的其它步骤。请注意,他们的成本并不相同。

成本变得重要的原因之一是因为移动端。

边城
边城
翻译于 08/11 20:52
0

移动端是一个谱系。

移动端是一个包含了便宜/低端、中端和高端设备的谱系

如果运气好,我们可能会遇到一个中高端的手机。但实事上并非所有用户都有这么好的设备。

用户使用的可能是中低端的手机,而且各类设备之间也存在极大的差异;热节流、缓存大小差异、CPU、GPU —— 最终,处理资源的时间会像处理 JavaScript 一样存在较大的差异,这些都取决于所使用的设备。使用低端手机的用户甚至可能就在美国

newzoo 发表了“对 23 亿 Android 智能手机的观察分析”。Android 在全球占有 75.9% 的市场A份额。预计 2018 年至少还有 3 亿的智能手机进入市场。其中有大量的 Android 设备。

下面是 2018 年各种硬件设备下对 JavaScript 解析时间的分析:

处理 (解析/编译) 1MB 未压缩的 JavaScript (压缩和 gZip 处理后 < 200KB)所需要的时间,这些时间是在真实设备上手工测得。(来源)

最上面是像 iPhone8 这样的高端设备,处理 JavaScript 相对较快。下面是一些普通手机,比如 Moto G4 和低于 100 美元的 Alcatel 1X。注意到处理时间的差异了吗?

随着时间的推移,Android 手机越来越便宜,而不是更快。这些设置的 CPU 频率更高,L2/L3 缓存也小得可怜。如果你期待用户都使用高端设备,那么你的目标用户就会减少。

我们从一个真实的网站来实际看看这个问题。下面是 CNN.com 的 JS 处理时间:

CNN.com 上的 JavaScript 处理时间,来自 WebPageTest (来源)

iPhone 8 (使用 A11 芯片) 在处理 CNN 的 JavaScript 比说通手机快 9 秒。这 9 秒可以让增强用户体验。慢得甚至需要特别说明:

对于 CNN.com,这个大量使用 JavaScript 的网站,比较中低端硬件通过 3G 访问加载的情况(来源)。Alcatel 1X 花了 65秒 才完成加载。

这暗示我们应该停止使用快速的网络和快速的设备。

有一些用户不会使用快速的网络,或者没有最新最好的手机,所以我们有必要从实际的手机和实际的网络环境开始测试。易变性确实是个问题。

边城
边城
翻译于 08/14 12:37
0

“可变性是杀死用户体验的原因” -  Ilya Grigorik。快速设备实际上有时可能很慢。快速网络可能很慢,可变性最终会使一切变得缓慢。

当差异可能会破坏用户体验时,使用缓慢的基线进行开发可确保每个人(快速和慢速设置)都能获益。如果您的团队可以查看他们的分析并准确了解您的用户实际访问您网站的设备,那么您将会提示您应该在办公室中使用哪些设备来测试您的网站。

                                            测试真实的手机&网络。

webpagetest.org/easy在“移动”配置文件下预先配置了许多Moto G4。如果您无法购买自己的中等级硬件进行测试,这将非常有用。

这里设置了许多配置文件,您可以使用这些配置文件已预先配置了常用设备。例如,我们有一些中等移动设备,如Moto G4准备测试。

在代表性网络上进行测试也很重要。虽然我已经谈到了低端手机和中位手机的重要性,但布莱恩霍尔特提出了这个观点:了解你的观众非常重要。

“了解您的受众,然后适当地关注应用程序的性能至关重要” -  Brian Holt(src)

并非每个站点都需要在低端手机上的2G上表现良好。也就是说,在整个频谱范围内实现高水平的性能并不是一件坏事。

谷歌分析>观众>移动设备>设备 可视化设备&操作系统访问您网站的位置。

您可能在频谱的较高端或频谱的不同部分拥有广泛的用户。请注意您网站背后的数据,以便您可以合理地调用所有这些内容。

如果您希望JavaScript快速,请注意低端网络的下载时间。您可以进行的改进包括:减少代码,缩小源代码,利用压缩(即gzip,Brotli和Zopfli)。

利用缓存重复访问。对于CPU速度慢的手机,解析时间至关重要。

如果您是后端或全堆栈开发人员,您知道您可以获得有关CPU,磁盘和网络的费用。

当我们构建越来越依赖JavaScript的网站时,我们有时会以我们并不总是容易看到的方式支付我们发送的内容。

硅谷课堂
硅谷课堂
翻译于 08/14 17:03
0

怎样才可以发送更小的 JavaScript

无论如何,只要我们能让发送出去的 JavaScript 最小,同还还能让用户有较好的体验,那么成功就在眼前。Code-splitting 就是一个能实现这一愿望的选择。

基于页面、路由或组件拆分巨大而完整的 JavaScript 包。如果“splitting”一开始就用在你的工具链中,你离成功就更近一步。

Code-splitting 的思想是这样的:不是向用户发送一个巨大的单个 JavaScript 文件 —— 就像一个巨大的披萨一样 —— 如果每次只给他们一块怎么样?只要有足够多片(但不是全部 —— 译者注)就能让当前页面跑起来。

Code-splitting 可以工作在页面、路由或组件级别。这对于很多现代的库和框架来说是件好事,这些库或框架可能通过 webpack 和 Parcel 生成脚本包。按照指南,可以将其用于 React、 Vue.js 和 Angular

在 React 应用中通过 React Loadable  添加 code-splitting。React Loadable 是一个高阶组件,它可以将动态导入封装在对 React  友好的 API 中,将 code-splitting 应用于给定的组件。

最近,很多大型团队看到了胜利背后的 code-splitting。

基于希望用户能快速进入交互的需求,Twitter 和 Tinder 积极采用代码分割方法来重写他们的移动 Web 体验,其交互性能提高了 50%。

像 Gatsby.js (React)、Preact CLI 和 PWA Starter Kit,通过尝试,在普通的移动硬件上达到了快速加载以及快速入进交互的效果。

这些网站还做了一件事情,就是采用审查,使其成为工作流程的一部分。

定期审查 JavaScript 包。像 webpack-bundle-analyzer 这样的工具非常适合用来分析构建出来的 JavaScript 包,以及可视代码的导入成本,这对于将本地化迭代工作流程错综复杂的依赖关系可视化非常有效。(比如,使用 `npm install` 导入包时)

值庆幸的是,JavaScript 生态系统中有大量优秀的工具可用于包分析。

边城
边城
翻译于 08/15 21:41
0
本文中的所有译文仅用于学习和交流目的,转载请务必注明文章译者、出处、和本文链接。
我们的翻译工作遵照 CC 协议,如果我们的工作有侵犯到您的权益,请及时联系我们。
加载中

评论(6)

SalemChen
SalemChen

引用来自“开源中国-首席村长”的评论

JS脚本为什么不能延迟加载/执行呢?也就是先加载渲染DOM元素,完成后再在后台下载js脚本文件再执行

引用来自“半世为仙”的评论

js有可能动态生成dom,改写当前dom,更新当前dom。如果先渲染dom,再加之js,那么用户所见有可能并不是所得。

引用来自“喻恒春”的评论

到目前为止, js 和 DOM 的主流关系依然是 动态和静态强耦合关系.
开发者难以做到彻底解耦, 为了快速开发大量使用"成品"库, 致使开发基于 "业务表现", 而不是剥离出的 "业务数据逻辑".
不深挖"数据逻辑", 还要快速开发, 还想降 JS 尺寸, 这世界哪里有这么好的事儿.

想降低尺寸很简单: 所有代码根据业务逻辑定制, 不被执行的代码通通剔除.
可有多少人会这么干呢?

有句话说的很有道: 解决了 10 个 BUG, 又创造了 11 个新 BUG
然后又来了12个需求....
莫默磨墨先生
莫默磨墨先生
这种就一个点深挖的优化,基本都是固定业务的某一个站。而现实中很多项目尤其是外包项目,追求的是快速实现,从一开始就追求极致的优化,不现实。
喻恒春
喻恒春

引用来自“开源中国-首席村长”的评论

JS脚本为什么不能延迟加载/执行呢?也就是先加载渲染DOM元素,完成后再在后台下载js脚本文件再执行

引用来自“半世为仙”的评论

js有可能动态生成dom,改写当前dom,更新当前dom。如果先渲染dom,再加之js,那么用户所见有可能并不是所得。
到目前为止, js 和 DOM 的主流关系依然是 动态和静态强耦合关系.
开发者难以做到彻底解耦, 为了快速开发大量使用"成品"库, 致使开发基于 "业务表现", 而不是剥离出的 "业务数据逻辑".
不深挖"数据逻辑", 还要快速开发, 还想降 JS 尺寸, 这世界哪里有这么好的事儿.

想降低尺寸很简单: 所有代码根据业务逻辑定制, 不被执行的代码通通剔除.
可有多少人会这么干呢?

有句话说的很有道: 解决了 10 个 BUG, 又创造了 11 个新 BUG
疯兔子
疯兔子

引用来自“开源中国-首席村长”的评论

JS脚本为什么不能延迟加载/执行呢?也就是先加载渲染DOM元素,完成后再在后台下载js脚本文件再执行
可以延迟加载,问题是js有可能会阻碍交互,例如:js没加载完,按钮有可能不能点击
半世为仙
半世为仙

引用来自“开源中国-首席村长”的评论

JS脚本为什么不能延迟加载/执行呢?也就是先加载渲染DOM元素,完成后再在后台下载js脚本文件再执行
js有可能动态生成dom,改写当前dom,更新当前dom。如果先渲染dom,再加之js,那么用户所见有可能并不是所得。
开源中国-首席村长
开源中国-首席村长
JS脚本为什么不能延迟加载/执行呢?也就是先加载渲染DOM元素,完成后再在后台下载js脚本文件再执行
返回顶部
顶部