从 HTTP 协议求解:为什么我们越来越不需要雪碧图了?

2,863 阅读6分钟

 关于作者

大家好,我是 kyrieliu,是在深圳打工的一枚前端工程师。
欢迎各位关注我的博客,有任何关于前端的讨论也可以加我的微信(K-I2ving)进行讨论,欢迎来访,共同成长

由一次面试引发的问题

前段时间面试了一位候选人,当我问到前端性能优化的常用手段时,候选人很笃定地说雪碧图,能减少页面请求数量,而且她经常在项目中使用 图片 但事实是,前端早就不用雪碧图了。

这篇文章会对前端性能优化宝典「雅虎军规」中关于「减少 HTTP 请求」的部分进行解读,并从 HTTP 协议的层面说明一个问题:现代前端工程中为什么不需要过于关注 HTTP 请求数了?

终端用户的等待时间中,有 80% 都耗在了前端上。而这些时间消耗中,大部分都用来加载页面所需的资源,比如:图片、视频、样式、脚本等。在这个问题上,终极的解决思路是「通过减少页面资源数来控制 HTTP 请求数」,而 HTTP 请求越少,呈现给用户的页面也就会越快。

毕竟,还记得这个听起来就很麻烦的过程吗 👇

但,总不能为了页面快速渲染,只把最简单的文字内容呈现给用户吧,就算咱们前端愿意,产品经理和设计师也是一千万个不愿意。

伴随着这几年网络内容的爆炸式发展,在终端呈现更多丰富的内容是一个不可逆的发展趋势,所以这个时代的前端工程师要在兼顾页面内容丰富基础上,不断加速网页的响应速度。

接下来会介绍一些上古时代的、关于减少请求数的前端性能优化法则(「雅虎军规」是在 2006 年底提出的),以及这些法则是如何在现代前端工程中落地 or 废弃的。

文件合并

文件合并,顾名思义,是把多个文件合并成一个文件(JavaScript 或 CSS),这么做的目的自然是为了减少文件的数量,从而减少 HTTP 请求的数量。比如我们现在有一个页面,页面上引用了来自不同域名的多个 JS 和 CSS,那么初始化这个页面时,需要发起的 HTTP 请求数就是这些文件个数的总和。之后有了 grunt、gulp 等工具的诞生,开发者在构建时会把多个同类型的文件打包为一个大文件,这样只需要发起很少的请求数就能获得相同内容的文件(或者说是“文件集合”)。

雪碧图

雪碧图原名是 「CSS Sprites」,原意是「精灵」,但恰好和「雪碧」的英文名一样,所以在中国大家都称其为「雪碧图」。

雪碧图的原理也很简单,把页面上(或是整个网站)上要用到的小图片合并成一张大图片,通过 css 的 background-image 和 background-position 来使用对应的图片。

但,由于这个概念提出的年代比较早,还没有诸如 gulp、webpack 之类的自动化构建工具,前端工程师们为了提高页面加载的性能,通常都需要自己制作雪碧图,可谓非常痛苦。

和雪碧图相似,还有个方案,叫做「映射图」。本质还是把很多张图片拼在一起来减少 HTTP 请求的数量。和雪碧图不同的是,映射图提倡把页面的所有原质量图片拼在一起。但由于使用场景有限(一般只能用在连贯图片展示的场景),所以很快就被抛弃了。

Base64 图片

在 Yahoo 军规中,base64 图片称作「inline images」。Base64 图片的本质是通过对一张图片进行 base 64 编码,生成的字符串可以用来在 html、css 文件中代替图片地址。

但是和其他优化手段不同的是,base64 不一定真的就意味着「优化」:当进行 base64 编码时,得到的字符串往往非常长,会变相增加 html 或 css 的体积,所以在使用时需要做一下权衡。一般我们在 webpack 工程中,会指定 8kb 以下的图片转用 base64 图片的形式。

但是,早就没必要这么玩了

在现代前端工程中,其实不太有人会 care 「合并文件以减少 HTTP 请求数」这个优化手段了,或者说,随着 HTTP 协议的更新换代,它甚至都不配作为一个「性能优化手段了」。

还记得 TCP 连接三次握手和四次挥手的过程吗?没关系,忘了的话我再贴一遍图。

每一个 HTTP 请求在发送出去之前,都会经历一次 TCP 三次握手的过程,当数据传输完毕,又进行一次四次挥手的过程,是不是贼麻烦?

上面这张图,没有错,但是(我又要说但是了),这已经是将近 20 年之前的事情了。回顾一下 HTTP 协议的发展史我们发现,完整的 HTTP 协议(也就是 HTTP 1.0)诞生于 1996 年,过了三年后就升级为 HTTP 1.1,到了 2015 年,再次升级为 HTTP 2.0。其实到 2021 年的今天,HTTP 已经进入 3.0 时代了。

严格意义上来说,合并文件这种性能优化手段,在 HTTP 1.1 推出后,就应该受到质疑了。为什么呢?且看下面这张图:

HTTP 1.1 版本默认支持长连接(Connection:keep-alive)和流水线请求(Pipelining),也就是说,多个 HTTP 请求可以依次共享同一个 TCP 连接,大大减少了建里 TCP 连接时的消耗和延迟。

到了 2015 年,HTTP 2.0 横空出世,带着多路复用的光环来了,不仅解决了 HTTP 1.1 流水线请求会造成的请求阻塞的问题,更重要的是,多路并行,更快了

下图是在相同的网路状况下,HTTP 1.1 和 HTTP 2.0 的直观对比图。

综上所述,在现代前端工程里,没有必要做这种「合并文件以减少 HTTP 请求」的“优化”,相反的,现在的前端应用都很大,反而需要用到「分包加载」和「懒加载」等手段,减少每个静态文件的实际体积,这样才能获得更快的下载速度以及在浏览器端获得更快的解析速度。

最后

如果我的内容对你有帮助,点个赞就是对我最大的鼓励~

如果有问题或者有争议的地方,也欢迎在评论区里理智拍砖~

也欢迎任何一位读到这里的小伙伴加我的微信(K-I2ving),前行路上不孤单(记得要备注“来自掘金”哦,我秒通过)。