JavaScript的开销(2018年篇)
创建一个具有交互性的网站需要将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去传递有用的信息。
最后,你的用户会感谢你的。