共计 4410 个字符,预计需要花费 12 分钟才能阅读完成。
创建一个具有交互性的网站需要将 JavaScript 发送给用户,而且经常会发送很多。你是否在手机应用上经历过点击链接或者滑动网页而没有反应?总的来说,JavaScript 在手机上还是最耗费性能的资源,因为它会在很多方面拖累网页的交互能力。上图是由 WebPageTest 测试的在 CNN.com 上 JavaScript 耗费的时间。高端的手机(如 iPhone 8)运行 JavaScript 用了约 4s 左右,而一般的手机(如 Moto G4)耗费了约 13s,低端手机(如 Alcatel 1X)耗费了约 36 秒
这篇文章会讲述提高 JavaScript 性能的一些策略:
为了提高速度,只加载当前页面所需的 JavaScript。先加载用户将会需要的代码,用代码分割对剩余代码的进行懒加载。这会使你在加载阶段和交互阶段最大限度的提高效率。默认使用基于路由的代码分割会带来巨大的好处。
学会接受性能目标。对于手机来说,压缩后 JS 的目标是小于 170KB,没有压缩的是 0.7MB。高性能对成功来说是至关重要的,当然这包括团队文化,结构和条例等因素。在没有性能目标的开发是将会是倒退的和失败的。
学会怎么审计和优化 JavaScript 包。当你用到一个库的一个功能时会发送整个库,对一些浏览器不需要的 polyfill,重复的代码,这些都是很可能发生的。
每一次交互都是新的交互的开始;要根据实际情况进行优化。网络状况差的手机时要降低包的大小,CPU 负担大的降低 JavaScript 解析时间。
如果运行在客户端的 JavaScrip 并不会提高用户体验,就要反思一下在服务端渲染是否会更快。服务端渲染和客户端渲染的选择如果做的不好可能会是一个很大的问题。
1. 网站从用户体验来看可能是臃肿的
当用户打开你的网站,你可能发送了很多文件,其中许多是脚本文件。从网站浏览者的来看,可能看起来像这样:尽管我很喜欢 JavaScript,但它始终是你网站最耗费性能的部分。我很乐意解释为什么 JavaScript 主要的问题。
现在网站发送压缩后的 JavaScript 的中位数是 350KB(查询网站,现在是桌面网站 398KB,移动端 384KB), 解压后浏览器需要运行 1MB 的脚本。
提示:如果你想要了解你的 JavaScript 包在用户与网站交互时的性能,请查看 Lighthouse。
JavaScript 的耗费时间主要取决于根据手机网络状况的耗费下载代码的时间和根据手机 CPU 耗费的运行时间。让我们来看看全球手机网络状况(中国是不是 GW 的原因,统计不了?)这张来自 OpenSignal 的图片显示了全球的 4G 情况和各个国家的网速。我们可以看到,很多国家的网速比我们想的慢的多。网站有时候发送好多兆的需要浏览器运行的代码,不管在 PC 端还是在移动端都到达了天花板。现在的问题是,你能负担的起这么多的 JavaScript 吗?
2. JavaScript 是耗费性能的。
提示:如果你发送了太多脚本,考虑使用代码分割进行打包或者用 tree-shaking 来提高 JS 的有效负荷。
现在的网站在它们发送的 JS 包中带有下面的东西:
客户端的 UI 框架。
全局状态管理方案(如 Redux)
Polyfills(经常是现在浏览器所不需要的)
全部的工具库(如 loash,monent)。
一套 UI 组件(如 Button,Input)。
这些代码一点一点的相加,最后代码越多,页面所需加载的时间越多。加载一个 web 页面就像解析一部电影的三个关键时刻。就是:它发生了吗?它真的有用吗?它真的可用吗?
它发生的时刻,就是你是否把内容显示在屏幕上(导航是否开始?服务器是否开始有相应?)
它真的有用吗,就是当你渲染完页面用户可以从这个体验中获得价值并且获得了所要的信息。
它真的可用吗,就是用户能够进行有意义的交互体验并且得到相应的反馈。
“交互”的真正的意义是什么呢?
一个网页具有交互性,它必须有能力对用户的输入有快速的反应。不多的 JavaScript 负荷可以确保快速反应。当用户点击一个链接或者滚动页面时,需要对他们的操作做出反馈。如果做不到这样的用户体验会使用户沮丧。人们在使用服务端渲染的页面时这种情况会经常发生,当后台发送一堆 JavaScript 包时,触发事件操作或其他额外的动作。当浏览器运行你所需要的许多事件时,用户输入的操作也要在相同的进程运行,这个线程被称为主线程。在主线程中加载太多 JavaScript 就是问题的所在。把 Js 发送给 Web Worker 或者通过 Service Worke 进行缓存,这样就会减轻对实时交互的影响。在主线程运行太多的 JavaScript 会对可视的元素的交互产生延迟,这对很多公司都是一个挑战
测试了 Google 新闻的交互时间,我们观察到高端手机(约 7S)和低端手机(约 55S) 有巨大的差距。那么,交互性的目标是什么呢?
我们觉得在缓慢的 3G 链接和中等性能的手机设备情况下最低的交互应保证在 5S 内完成。“但是我的用户都连接快速的网络和高端手机!”真的吗?你可能连接咖啡店里“快速”的 wifi,,但是只有 2G 或者 3G 的速度。还有各种其他的原因都会影响使用手机的效率。
谁减少了 JavaScript 的加载并且降低了交互时间?
Pinterest(类似于 Tumblr 的照片分享网站)将网站的 JavaScript 包从 2.5MB 减少到小于 200KB,并且可以开始交互时间从 23s 减少到 5.6s 网站的收入提高了 44%,登录数量提高了 753%,手机端的周活跃数提高了 103%。
AutoTrader 将网站的 JavaScript 包大小减少了 56%,并且加载时间降低了约 50%。
Nikkei 将网站的 JavaScript 包大小减少了 43%,并且加载时间减少了 14s。
让我们设计更有弹性的网站,而不是依赖很多的 JS 负荷。交互性受很多东西的影响。它可能受手机数据流量的限制,或者咖啡店 wifi 速度,又或者是断断续续的网络连接。
当这些情况发生时,网站可能需要运行很多 js,用户可能在页面显示之前关闭网站。或者在页面显示之后,用户还需等一段时间才能开始交互。理想的情况是,传递越少的 JS 会缓解这些问题。
3. 为什么 javascript 这么耗费资源
为了解释 javascript 为什么这么耗费性能。我需要带你观察当你向浏览器发送内容时发生了什么,一位用户把 url 输入地址栏后发生了什么。
当一个请求发送给服务器。服务器会返回一资源。然后浏览器会解析这些资源并且发现必要的 CSS,Javascript,图片等等。最后浏览器运行所有的资源。这当中的一个问题是 javascript 往往成为阻碍性能的一个瓶颈。我们都想尽快的绘制画面,是页面具有交互功能。但是当 javascript 成为那块短板后,你只能看着画面而不能进行交互。
在开发中想要加快运行 javascript 我们要记住一件事,我们一定要快速的下载,解析,编译,执行它。
这意味着我们需要在网络上加快传输,在应用端加快运行。如果 javascript 引擎在解析编译阶段耗费了大量时间,这会造成用户相同时间的延迟交互性体验。下图是 V8 引擎运行不同网页时各个阶段耗费的时间:
橘黄色表示网页接受 js 后解析所需要的时间。黄色部分表示编译所需要的时间。这两个阶段相加占到了总时间的 30% 以上。尽管 V8 在一个独立的用于编译的线程(background thread)编译 js,降低了 20% 的编译时间,但是编译和解析依然非常耗费时间,很少见到一个大型的脚本耗费时间低于 50ms 的。
另一件需要注意的是相同大小的不同资源所需加载的时间是不同的。200KB 的代码和 200kb 的图片的性能开销的差别是巨大。
它们下载的时间可能是相同的,但到运行阶段所耗费的时间是截然不同的。一张 JPEG 的图片需要解码,栅格化(rasterized),和在屏幕上绘制。一个 JavaScript 包需要解析、编译、执行而且还有一系列的步骤需要引擎完成。请注意着不同的差别。
4. 手机有高低好坏的差别。
如果我们幸运,我们可能会有高端或者不错的手机。然而现实是不是所有的用户都有这些设备的。用户可能是低端的或者中等手机。这些手机等级的差别同同样会带来严重的消耗。散热器(thermal throttling), 内存、cpu、gpu 的不同, 会给用户带来非常大的差别。你的使用低端手机的用户甚至可能在美国。
下面是 2018 年不同手机在解析 javascript 的差别:
在顶部像 iphone8 这样的高端设备运行脚本相对快一些,在一般的 Moto4 和更低端的 Alcatel 1X 则运行的慢一些。注意到运行时间的差别了吗?安卓手机一般便宜些,但运行慢,这些设备通常搭载低端的 cpu 和较小的内存,如果你理所当然的认为用户都用高端手机,你将失去这些用户。
一些用户没有较快的网络速度或者拥有最新的最好的手机,所以用真实的手机和网络测试是重要的。不同的情况是一个需要认真考虑的问题。
当不同情况会使用户体验变差,制定出一条慢速的底线会使所有人收益。如果你的团队能看一下分析报表了解一下用户的真实情况,会让你知道到底用什么手机进行测试。
5. 如何降低发送的 javascript 代码体积
成功的关键在于发送最少的脚本,让用户获得有用的体验。代码分割是一个不错的选择。代码分割就是像把一个巨大的披萨切成几块,然后一次给用户一块,把代码分成几部分,只发送当前页所需要的代码其他代码,在需要的时候再发送。代码分割可以应用在页面、路由和组件层面。代码分割通过像 webpack 和 parcel 这样的打包器被现代框架完美支持。
另一个方面,可以把代码审计带入到工作流程中。幸运的是,现在的生态游许多工具可以帮助代码分析。这些工具让你的 javascript 包的内容可视化:他们标记了大的库,重复代码,你可能需要的依赖。代码审计同时标记出可以将重量级的包(如 Moment.js)替换成轻量级的包(如 date-fns)。如果你正在使用 webpack,你可以我们的 github 里面找到常见库的问题。
Get fast, stay fast.Performance is a journey. Many small changes can lead to big gains.
Enable users to interact with your site with the least amount of friction. Run the smallest amount of JavaScript to deliver real value. This can mean taking incremental steps to get there.
In the end, your users will thank you.
6. 变得更快,保持更快
性能的表现就像一场旅行。许多小小的改变就能获得巨大的收益。使你的用户在浏览网页时获得更好的用户体验。为了一步一步的接近那个目标,用尽可能小的 JavaScript 去传递有用的信息。最后,你的用户会感谢你的。