前端性能优化系列之网络篇

1,487 阅读10分钟

前言

      一个应用,性能好意味着首屏渲染快、白屏时间短、交互流畅,可以提升留存率和转化率,因此性能优化对产品来讲很重要。性能优化的知识点比较的零散,也很考验一个程序员的基础功底,很多面试都会问到这个问题(我也喜欢跟候选人从这个话题展开来聊、很遗憾的是很少有候选人能达到及格的程度),因此准备了一个系列文章来讲讲从前端的角度来看,如何做性能优化。

      性能优化考验的是综合能力,对知识的深度和广度以及工具的综合运用,需要知道一个应用从发起请求到使用交互的完整流程、流程中哪些环节可以做优化、优化方案如何做、在项目中如何操作实施。本系列文章将会从网络篇、运行篇、构建篇、工具篇这几个角度逐步阐述。

网络篇总览

https://www.w3.org/TR/2012/REC-navigation-timing-20121217/#sec-navigation-timing-interface

先来看一张W3C给出的一个请求的完整流程。

从图中我们可以看到,请求的流程可以分成三部分,请求前、发送请求、收到响应。接下来我们就围绕以下几部分进行优化讲解:

  1. 减少请求的数量
  2. 减少请求前准备的时间、请求携带的数据和响应体的大小
  3. 提前准备好数据、数据的复用
  4. 请求尽量并行,减少串行

Http请求

1. 尽量避免重定向

每一次的重定向都会进行一次完整都http/https流程,网络上的耗时是占比非常大的一部分。

上图可以看到,我的一次页面访问就多耗费了134ms。

当然,有一些请求的重定向是不可避免的,比如域名变了,为了不影响使用旧域名的用户,需要将旧域名重定向到新域名;比如一些广告业务,为了保证业务数据的携带经常会进行1次以上的重定向。

2. dns预解析

利用dns-prefetch,使页面加载时,同步在后台进行dns的预解析。参考MDN

将应用中用到的域名配置dns预解析,降低初次请求时dns解析的时间。

<link rel="dns-prefetch" href="//s1.qhimg.com"/>

3. 资源预处理

  1. 资源prefetch 页面加载时,同步在后台进行资源的下载。参考MDN
<link rel="prefetch" href="assets/index.png">
<link rel="prefetch" href="second.video">
  1. 资源preload 页面加载过程中(在浏览器的主渲染机制介入前),进行资源的下载。参考MDN

对于有些资源是在页面加载完成后即刻需要的,可以配置为preload。常用的有字体文件,首屏用到的css、js等。

<link rel="preload" href="main.css">
<link rel="preload" href="main.js">
<link rel="preload" href="fonts/index.eot">

3. http缓存:强缓存和协商缓存

HTTP缓存一般分为两类:强缓存和协商缓存。

先来一张图,看看这两种类型的缓存表现为什么样子。

强缓存:

强缓存是读取速度最快的一种缓存方式,因为不需要请求服务端产生网络消耗,直接从本地读取,只要资源还在缓存有效期内就能生效。

如上图中Size为disk cache和memory chache这两种都是本地缓存,可以看到强缓存的status表现为200(chrome)。 另外从图中可以看到memory cache的Time为0ms,因为从应用内存中读取数据的时间几乎可以忽略;disk cache的Time很小,只有几毫秒,这是磁盘IO的开销。

控制强缓存的header字段为Expires和Cache-Control,这两个字段表示缓存有效时间是多久。如果两个字段同时存在,则Cache-Control优先级更高。

协商缓存:

协商缓存,顾名思义就是浏览器与服务器协商,协商的是服务器的内容是否有变化,如果没有变化,则使用本地的缓存,如果有变化,则返回新的内容。

如上图中status为304就是协商缓存,304状态码表示文件为变化(chrome)。

控制协商缓存的header字段为Etag和last-modified。Etag用来表示资源的唯一性,不同的资源Etag值不同;last-modified用来表示资源上次修改时间。

这两个字段可以一起使用,服务会优先判断Etag,Etag相同时再判断last-modified。由此也引发出一个问题,为什么有来last-modified还需要Etag:

  1. 一些文件也许会周期性的更改,但是他的内容并不改变(仅仅改变的修改时间),这个时候我们并不希望浏览器认为这个文件被修改了;
  2. 某些文件修改非常频繁(比如1s内修改了N次),而last-Modified的值是精确到s级别的;
  3. 某些服务器不能精确的得到文件的最后修改时间。

最后用一张图来表示缓存的流程。

附.如何让缓存失效:

  1. chrome devtools勾选disable cache

  2. 页面的强刷新:command + F5、地址栏敲回车

  3. 代码中不希望资源命中缓存怎么做,通常是改变资源的url的无意义参数(因为http缓存是以完整的url做为索引的)。

- <img src="https://hao5.qhimg.com/t01d1024d05f4567a06.gif" />
+ <img src="https://hao5.qhimg.com/t01d1024d05f4567a06.gif?t=1323423" />

4. 减少http请求数

由于网络上的耗时是非常大的,所以我们需要将一些小的http请求进行合并,以减少http请求数。

常见做法有:

  1. 对js、css等静态资源进行合并,比如几个css文件合并成1个css文件。
  2. css中用到的一些小图标合并成雪碧图。
  3. base64的使用:一些复用性不高的小图片可以转成base64,减少http的请求数。
  4. 可以使用css实现的效果就不要用图片来解决。

注:在此需要说明的是,减少请求数与减少响应体的大小看起来是个悖论,因此我们需要进行平衡,在能充分利用浏览器并发能力的前提下进行请求数和响应体大小的平衡优化。后面的内容也会说到请求并发和响应体大小这两个问题。

5. 减小请求头的大小:合理管理使用cookie和域名

  1. 对cookie的写入进行管理,禁止太大对内容和非必要的内容对cookie进行写入。
  2. 对cookie写入对域名进行管理,根域名的cookie写入需要进行严格限制。
  3. 功能性域名与非功能性域名分开,通俗讲就是静态资源的域名要与网站、接口的域名区分开,减少静态资源请求头的大小。

6. 减小请求的响应体大小

  1. 对js、css等资源进行代码压缩。
  2. 对图片等资源,在不影响视觉的前提下,进行压缩;
  3. 针对不同浏览器,进行不同格式的加载(比如chrome下使用webp)
  4. 对请求体进行gzip等压缩,减少请求体的大小

7. 使用cdn

静态资源(css、js、图片等)和不变内容类接口使用cdn。

  1. 可以减小主应用服务的过载压力,提高主应用的响应速度。
  2. 用户就近获取所需内容,提高用户访问响应速度和命中率。
  3. 使用cdn后,cdn的域名与主应用域名不一样,天然对cookie进行隔离。

8. 本地缓存内容使用

合理利用localStorage、sessionStorage、indexedDb、web SQL等缓存技术,将可重复利用数据或者非实时信息内容缓存浏览器端,进行二次访问重复利用,提升页面内容展示速度。

9. 占位图使用

使用占位图,使用户视觉上有内容,是避免白屏时间过长的一种有效手段。但是使用占位图有2点需要注意下:

  1. 整个应用统一使用一套占位图,设置强制缓存,有效期可以设置长一些。
  2. 占位图或者占位图所在容器要与最终展示内容尺寸保持一致,否则会出现闪屏效果。同时由于触发回流,性能受损。

10. Service Worker

利用service workers进行资源的离线缓存,用户再次访问时,可以利用离线缓存迅速打开应用。

离线缓存是service worker最重要的能力之一,利用它可以做成PWA应用。

注:必须是https应用。

11. 利用浏览器的并发数限制

当前主流浏览器对TCP连接的并发请求数控制在6个左右(chrome、firefox为6个),在目前普遍过百的整页请求数的前提下,无疑是个瓶颈,因此衍生了使用domain hash技术来使用多个域名加大并发量(因为浏览器是基于domain的并发控制,而不是page),但是过多的散布会增加dns解析的负担,因此限制在4个左右为宜。

比如我们有(s1~s4).qhimg.com 四个域名,这样就需要我们在代码中保证,对于使用到的静态资源,我们要动态分配请求的资源域名落在s1~s4之间。

比如360导航的静态资源就散列在hao1~hao5这几个域名下。

http2.0进阶

http2.0带来了四个主要的特性:二进制、header压缩、多路复用和服务器推送。

其中header压缩和多路复用都带来了性能的提升。

  1. header压缩 HTTP1.1每一次通信都会携带一组头部,用于描述这次通信的的资源、浏览器属性、cookie等。而在HTTP2下:
  • HTTP/2在客户端和服务器端使用“首部表”来跟踪和存储之前发送的键-值对,对于相同的数据,不再通过每次请求和响应发送;
  • 首部表在HTTP/2的连接存续期内始终存在,由客户端和服务器共同渐进地更新;
  • 每个新的首部键-值对要么被追加到当前表的末尾,要么替换表中之前的值。
  1. 多路复用 http1.1下,想要并发多个请求,必须使用多个TCP连接,并且浏览器为了控制资源,对单个域名还有6个左右的连接数限制,超过限制数量的就需要pending。

http2.0下,多路复用的这个特性使得性能极大提升:

  • 同个域名只需要占用一个 TCP 连接,消除了因多个 TCP 连接而带来的延时和内存消耗。
  • 单个连接上可以并行交错的请求和响应,之间互不干扰。

chrome devtools网络相关工具

  1. Network
  • 在network中,可以查看http请求的状态、类型、请求大小和耗时,可以筛选查看不同类型的请求。

  • 可以禁用缓存,这在开发过程中非常重要,避免由于缓存导致看不到修改后的效果。

  • 点击一条网络请求,可以查看其在各个阶段的耗时。

  • Http的 request headers和respond headers

  1. Application