反直觉!浏览器到底是如何下载资源的

10,379 阅读5分钟

今天文章来聊聊浏览器到底是如何对待下载资源的,比如说 JS、CSS、字体、图片等文件。

CSS、JS 文件冤家路窄

HTML 在解析的过程中遇到同步的 script 会卡住 DOM 解析这个知识点我相信读者们应该都知道。

<!DOCTYPE html>
<html lang="en">
<body>
    <script>console.log(1)</script>
    <div>1</div>
</body>
</html>

上述代码中,HTML 遇到 <script>console.log(1)</script> 代码就会停止 DOM 解析了。当然现代浏览器不是说就这样停住啥也不干了,会有个 preload scanner 去扫描底下的文件,然后根据文件类型去制定优先级(这个下面再说)。

上面举得例子是内联代码,如果是一个 JS 文件的话,那么就得等这个 JS 文件下完以后执行代码才行。

但是一般我们文件结构不会那么单一,CSS 文件也肯定是有的。因为 JS 不仅能动 DOM 也能改 style,所以如果遇到 script 之前还有别的 CSS 文件的话,浏览器还得等 CSS 文件下完以后再去执行 JS。

所以说不只是 JS 会阻塞 DOM 解析,CSS 也会。

当然我想现在应该很少再有人直接写同步的 script 了吧。

文件同时遇到,那会同时开始下载嘛?

举个例子,在可以同时下载多个文件的情况下,浏览器在 HTML、CSS 文件中解析到了 img、font、background-image 资源,那么们会同时开始下载嘛?

答案是不会!

这里只有 img 会最先开始下载,其它两块内容都得等到 layout 以后才会开始下载。不信的话我们来看图:

这里以 font 为例

WechatIMG211

图中右下角框出来的是字体文件,上面是 CSS 文件,字体是从 CSS 文件中得知需要去加载的。

大家可以在图中发现 CSS 文件早早开始下载解析了,但是 font 文件迟迟未开始下载,直到 FP 指标即将出现的时候才开始下载动作,这个时间点是在 layout 完成以后。

图中是以掘金为例,大家可以多试试别的网站,应该能发现是符合预期的。

那么读者可能会有个疑问,按照这个逻辑意思是说能用 img 的都用 img,因为background-image 不是马上开始下载的?道理是这个道理,但是还是得分情况来说,如果图片出现的位置不是首屏,用 background-image 也没啥问题,当然给 img 用上懒加载也能解决问题。

那么到底文件的优先级是如何的?

如果大家有使用过 Performance 测试网站性能的话,可以在 Network 那栏里 hover 下载的资源,此时你能发现资源有优先级的显示。

2151614520163_.pic 2171614520185_.pic 2141614520153_.pic 2161614520170_.pic 2181614520195_.pic

从图中大家可以发现 CSS 和字体资源的优先级的最高的,JS 文件有高有低(这和类型有关),图片、svg 的优先级为低。

这部分的优先级是指某个时间点上的优先级,而不是适应于整个周期。对于 CSS 文件优先级最高还是挺好理解的,毕竟 JS 文件依赖于 CSS 文件,更加详细的内容大家可以参考 从Chrome源码看浏览器如何加载资源 中的「3. 资源优先级」内容。

网络协议才是最大元凶

浏览器要下载资源,TCP 协议肯定得用上,但是 TCP 协议天生就是一个慢的东西。

先得握手,然后慢开始算法,就和我们用迅雷下载东西一样,带宽很大没用,速度都是得爬升上去的。如果再加个 HTTPS 的话,还得再多个 TLS 的握手。

然后说回 HTTP1.1 协议。Chrome 只支持同个 domain 同时并发 6 个请求,所以在之前的协议中一堆请求都得 block 住等之前的下载完成。

当然现在不同了,HTTP2 的概念基本大家都知道了,没见过猪跑但吃过猪肉,对于这块的知识点聊上几句没啥问题。什么多路复用,header 合并、二进制帧啥的。

得利于 HTTP2 协议中的多路复用,我们不再被浏览器的六个并发所限制,所有同一个 domain 上的请求都能跑在复用的六个网络通道上,下载耗时瞬间减少了几个量级,但是大家有没有考虑过这个 HTTP2 到底是如何对待这些资源的?

如何分配它们的优先级?依赖?带宽或者别的?毕竟一堆资源占用同一个网络通道,那么到底该谁先下载,谁享有的带宽多点都是需要协调的。否则假如一堆图片占用了大部分带宽,其他 CSS、JS 文件下的很慢的话,就会影响整个网页的性能了。

比如说我现在需要下载 CSS、JS、图片和字体这些文件。举个例子在这些文件中我希望 CSS 文件能最先开始下载,并且能分配到的带宽多点;JS 文件呢可以等 CSS 文件下完再去下;其他文件呢优先级比 CSS 文件低点,但是也能够享受到带宽去进行下载。

这个需求我们可以通过二进制帧去实现。HTTP2 协议共存在十个二进制帧,其中你可以通过 HEADERS 帧分配新的优先级,也可以通过 PRIORITY 帧更改优先级。

对于流的优先级,我们可以通过两个方式来实现调整:

  • 当我们调整流的依赖时,就可以实现让 JS 文件等待 CSS 文件下载完毕以后再开始
  • 当我们调整流的 weight 时,就可以实现带宽的分配,让某些资源能更快地进行下载

WX20210228-223401@2x

如图所示(丑了点)。第一排的文件开始一起下载,weight 相加为 20,那么通过计算可得 CSS 文件享有带宽的一半,其他各占有 1 / 4,JS 文件得等 CSS 完成以后才开始。

那么该如何设置这些东西呢?当然得服务端支持啦,一般都指 CDN 服务商了。

最后

以上就是全部内容了,大家有想探讨的可以一起交流。