前端优化之 Http 相关优化总结

2,613 阅读10分钟

Http 优化方式是前端性能优化的重要部分,也是前端必备的知识点之一。

减少静态资源文件大小

这个是最根本的途径,假设真的有个 10 几兆以上的静态资源文件,不减少大小的情况下,即使优化做到了极致,用户体验也好不了哪里去。

如果整个网页就 2KB 大小的资源文件,不优化都很快。

代码层优化

  • 只打包用到的依赖包,目前 webpack tree shaking 功能已经自动处理了,还有尽量少使用第三方依赖包(当然看情况啦)。
  • 代码分割(code splitting),不同页面加载自己用到的代码,不加载其他页面的代码(其实也属于懒加载)。

传输层优化

Http 传承启用压缩传输方式。

一般我们开启 gzip,基本都能压缩 6 倍左右(一般都是文件越大,字符串相似率越大,压缩率越大)。

首先经过服务器压缩后,然后 Http 响应头 Content-Encoding 设置为相应的压缩方式,浏览器会自动解压的。

Content-Encoding: gzip

当然还有其他的压缩方式,如 compress、deflate 等等,目前使用最广的还是 gzip。

适当合并或者分散请求

合并请求或者分散请求需要看实际情况的。

合并请求

http 1.1 (包括 http1.1)之前的版本,浏览器存在同域名并发限制,谷歌目前是同域名并发现在为6 个请求,其他的浏览器或多或少,但也差不了多少。

如果是使用的是 http1.1 web 服务,那么我们首次加载的资源要基本保证在 4 个以内,所以静态资源请求数过多就要看情况进行合并请求了。

分散请求

Http2.0 没有同域名并发的问题,我们可以适当分散请求。当然如果在 Http1.1 一个资源文件过大,然后并发并没有达到限制,也可以拆分资源文件达到分散请求的目的。

使用预加载

预加载某些情况下可以大大提升加载速度进而提示用户体验。

预加载需要了解 preloadprefetch 的知识。

预加载 DNS

dns 解析也是需要时间的,特别在移动端的时候更明显,我们可以预解析 dns 减少不通域名 dns 解析时间。

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

其实还有个 preconnectpreconnect 不仅完成 DNS 预解析,同时还将进行 TCP 握手和建立传输层协议,但是浏览器有兼容性,目前用不上。

<link rel="preconnect" href="http://example.com">

预加载静态资源

使用 preload

通过 preload 一般是预加载当前页面要用到的图片、字体、js 脚本、css 文件等静态资源文件。

场景一

如果需要,你可以完全以脚本化的方式来执行这些预加载操作。例如,我们在这里创建一个HTMLLinkElement 实例,然后将他们附加到 DOM 上:

var preloadLink = document.createElement("link");
preloadLink.href = "myscript.js";
preloadLink.rel = "preload";
preloadLink.as = "script";
document.head.appendChild(preloadLink);

这意味着浏览器将预加载这个JavaScript文件,但并不实际执行它。

如果要对其加以执行,在需要的时候,你可以执行:

var preloadedScript = document.createElement("script");
preloadedScript.src = "myscript.js";
document.body.appendChild(preloadedScript);

当你需要预加载一个脚本,但需要推迟到需要的时候才令其执行时,这种方式会特别有用。

场景二

字体是要使用到的时候才会去加载字体的(如果字体是自定义的字体,会发起 Http 请求加载字体)。

由于这个特性,我们可以预加载字体,待使用到字体的时候,字体已经加载完毕,无需等待加载。

如下我们没有 preload 的时候,代码也是可以运行的,但是字体加载是需要等待页面 JS、CSS 资源加载完毕后,当前页面使用到字体才会去加载的:

<style>
  @font-face {
    font-family: Test-Number-Medium;
    src: url(./static/font/Test-Number-Medium.otf);
  }
</style>

我们加上:

<link rel="preload" href="./static/font/Test-Number-Medium.otf">

就可以提交加载,节省大部分甚至全部的字体加载时间,一般都是全部的时间,因为 JS 资源文件比字体大多了(并行下载,最长的资源加载时间,决定了最大加载时间)。

使用 prefech

prefetch 一般是预加载非当前页面的资源,prefetch 是一个低优先级的资源提示,允许浏览器在后台(空闲时)获取将来可能用得到的资源,并且将他们存储在浏览器的缓存中。当前页面加载完毕,才会开始下载 d带有 prefetch 标记的资源,然后当用户进入另外一个页面,已经 prefetched 的资源可以立刻从缓存中加载。

不过 prefech 的应用场景比较少。

<link rel="prefetch" href="/uploads/images/pic.png">

采用懒加载

图片懒加载

这种做法一般都是在用户滚动到响应位置(当然从用户体验式来说,需要提前一点加载),才会加载响应的图片,图片特别多的网上基本都会做这个优化(如视频网站)。

或者幻灯片查看图片的时候,用户即将查查下一张图片的时候再加载,而不是一次性加载全部的图片。

JS 懒加载

需要用到相关 JS 时,通过动态创建 <script> 标签进行 JS 文件懒加载,如 Webpack 的 code splitting

合理使用 defer 和 async

带有 deferasync 属性的 script 资源都会并行下载,而且不会影响页面的解析,从而达到了节省脚本下载时间

两种的不同的在于:

带有 defer 属性的资源会按照顺序在页面出现的属性,资源加载完后,会在 DOMContentLoaded事件调用之前依次执行。

带有 async 属性的资源则是下载完立即执行,可能在 DOMContentLoaded 事件之前或者之后执行,多个带有 async 属性的资源无执行顺序,谁先加载完成,谁先执行。

利用缓存

缓存对于再次访问相同资源来说,是个极大的优化,缓存是 http 优化的必经之道。对于 css 和 js 这些静态资源文件,我们一般都是用强缓存(例如缓存30天),强缓存无需再次向服务请求静态资源。 但是强缓存如果使用不当,那么会对用户造成意想不到的 bug,如入口 html 文件就不能被强缓存了,否则版本更新后,用户在缓存期间是无法访问到新版的页面。

详细可以看下本人的另一篇缓存相关的文章,浏览器之HTTP缓存的那些事

使用 Http2.0

Http2.0 多路复用解决了多域名并发现在问题,可以节省资源总体的下载时间,还有请求头压缩和差异传输也会提高传输效率。

多路复用

HTTP1.1 持久连接解决了连接复用问题,但还是存在着一个问题,一个 TCP 无法并发处理请求:在一个 TCP 连接中,同一时间只能够发送一个请求,并且需要等响应完成才能够发送第二个请求。

HTTP2.0 使用了多路复用的技术,做到同一个连接并发处理多个请求,而且并发请求的数量比 HTTP1.1 大了好几个数量级。

当然 HTTP1.1 也可以多建立几个 TCP 连接,来支持处理更多并发的请求,但是创建TCP连接本身也是有开销的。

TCP 连接有一个预热和保护的过程,先检查数据是否传送成功,一旦成功过,则慢慢加大传输速度。因此对应瞬时并发的连接,服务器的响应就会变慢。所以最好能使用一个建立好的连接,并且这个连接可以支持瞬时并发的请求。

多路复用能带来哪些优化呢?

有多路复用特性,那么浏览器对同一域名的链接数的限制也是没必要的了(HTTP1.1 的谷歌对统一域名并发请求最多支持 6个 持久链接)。

那么我们可以根据实际情况进行资源拆分,从而节省下载时间,无并发请求限制的情况下,下载的时间是根据并行下载的最长时间来算的,无需等待上一个资源下载,才能进行另外一个资源的下载,在资源比较多的情况下,这将大大提升资源总体的下载速度。

以前的 CSS 的雪碧图 优化手段,在多路复用的特性下,已经是没必要的了。

多路复用还带来了,延迟低的优化,这也是速度提升的一方面。

二进制分帧

在应用层与传输层之间增加一个二进制分帧层,以此达到在不改动 HTTP 的语义,HTTP 方法、状态码、URI 及首部字段的情况下,突破 HTTP1.1 的性能限制,改进传输性能,实现低延迟和高吞吐量。

在二进制分帧层上,HTTP2.0 会将所有传输的信息分割为更小的消息和帧,并对它们采用二进制格式的编码,其中HTTP1.x 的首部信息会被封装到 Headers 帧,而我们的 request body 则封装到 Data 帧里面。

首部字段压缩

HTTP2.0 使用 **HPACK **算法对首部字段的数据进行压缩,这样数据体积小了,在网络上传输就会更快。

首部字段差异传输

HTTP2.0 规定了在客户端和服务器端会使用并且维护首部表来跟踪和存储之前发送的键值对,对于相同的头部,不必再通过请求发送,只需发送一次。

事实上,如果请求中不包含首部字段(例如对同一资源的轮询请求),此时服务器自动使用之前请求发送的首部字段,那么首部字段开销就是零字节。

如果首部发生变化了,那么只需要发送变化了数据在 Headers 帧里面,新增或修改的首部帧会被追加到首部表首部表在 HTTP2.0 的连接存在期内始终存在,由客户端和服务器共同渐进地更新。

使用 CDN

严格意义上,CDN 不算 Http 优化,前端也无法直接处理这个事情,这是运维的事。CDN 节点可以解决跨运营商和跨地域访问的问题,提升访问速度。

CDN的全称是 Content Delivery Network,即内容分发网络。CDN 是构建在网络之上的内容分发网络,依靠部署在各地的边缘服务器,通过中心平台的负载均衡、内容分发、调度等功能模块,使用户就近获取所需内容,降低网络拥塞,提高用户访问响应速度和命中率。CDN 的关键技术主要有内容存储和分发技术。—— 科学百科

CDN 有什么优势?

CDN 最大的优势在于提升用户资源访问速度,因此静态资源走 CDN 是一个很好的优化点。 分布式服务器,用户就近访问,CDN 节点可以解决跨运营商和跨地域访问的问题,同时分散源服务器访问压力。

还有一个而外的优点: 无 cookie 传输(其实这个不完全算是优势)。静态资源一般无需 cookie,静态资源放在不同域名可以减少一定程度的带宽和提升一定的访问速度,虽然单个请求不明显,但是量多了是会有质的区别的。

CDN 是如何分散源服务器的压力的?

CDN 的核心点有两个: 一个是缓存,一个是回源。

通过缓存和回源策略,达到分散源服务器的压力。首先将从根服务器请求来的资源按要求缓存。然后当有用户访问某个资源的时候,如果被解析到的那个 CDN 节点没有缓存响应的内容,或者是缓存已经到期,就会回源站去获取。