浏览器从输入url到页面呈现过程(持续更新)

349 阅读8分钟

1、写在前面

个人觉得作为前端开发,浏览器的基本原理尤为重要。可能很多web开发者刚入行时就已经开始用框架做业务层面的开发,但是对于一些web基础知识和底层原理却一知半解。本文是个人对三元大神有关浏览器的文章拜读之后的总结,原文链接

2、浏览器缓存

浏览器中的缓存作用分为两种情况,一种是需要发送HTTP请求,一种是不需要发送。

强缓存

浏览器首先会检查强缓存,这个阶段不需要发送HTTP请求。

  • 检查方式

    在HTTP/1.0和HTTP/1.1当中,这个字段是不一样的。在早期,也就是HTTP/1.0时期,使用的是Expires,而HTTP/1.1使用的是Cache-Control。

  • Expires

    Expires即过期时间,存在于服务端返回的响应头中,告诉浏览器在这个过期时间之前可以直接从缓存里面获取数据,无需再次请求。

    这个方式看上去没什么问题,合情合理,但其实潜藏了一个坑,那就是服务器的时间和浏览器的时间可能并不一致,那服务器返回的这个过期时间可能就是不准确的。

  • Cache-Control

    在HTTP1.1中,采用了一个非常关键的字段:Cache-Control。这个字段也是存在于它和Expires本质的不同在于它并没有采用具体的过期时间点这个方式,而是采用过期时长来控制缓存,对应的字段是max-age。比如:Cache-Control:max-age=3600代表这个响应返回后在 3600 秒,也就是一个小时之内可以直接使用缓存。

    其他指令:

    • public: 客户端和代理服务器都可以缓存。因为一个请求可能要经过不同的代理服务器最后才到达目标服务器,那么结果就是不仅仅浏览器可以缓存数据,中间的任何代理节点都可以进行缓存。
    • private: 这种情况就是只有浏览器能缓存了,中间的代理服务器不能缓存。
    • no-cache: 跳过当前的强缓存,发送HTTP请求,即直接进入协商缓存阶段。
    • no-store:非常粗暴,不进行任何形式的缓存。
    • s-maxage:这和max-age长得比较像,但是区别在于s-maxage是针对代理服务器的缓存时间。

    当Expires和Cache-Control同时存在的时候,Cache-Control会优先考虑。 当资源缓存时间超时了,也就是强缓存失效了,就进入到第二级屏障——协商缓存了。

协商缓存

强缓存失效之后,浏览器在请求头中携带相应的缓存tag来向服务器发请求,由服务器根据这个tag,来决定是否使用缓存,这就是协商缓存。

具体来说,这样的缓存tag分为两种: Last-Modified 和 ETag。

  • Last-Modified

    最后修改时间。在浏览器第一次给服务器发送请求后,服务器会在响应头中加上这个字段。 浏览器接收到后,如果再次请求,会在请求头中携带If-Modified-Since字段,这个字段的值也就是服务器传来的最后修改时间。

    服务器拿到请求头中的If-Modified-Since的字段后,其实会和这个服务器中该资源的最后修改时间对比:

    如果请求头中的这个值小于最后修改时间,说明是时候更新了。返回新的资源,跟常规的HTTP请求响应的流程一样。否则返回304,告诉浏览器直接用缓存。

  • ETag ETag 是服务器根据当前文件的内容,给文件生成的唯一标识,只要里面的内容有改动,这个值就会变。服务器通过响应头把这个值给浏览器。 浏览器接收到ETag的值,会在下次请求时,将这个值作为If-None-Match这个字段的内容,并放到请求头中,然后发给服务器。服务器接收到If-None-Match后,会跟服务器上该资源的ETag进行比对:

    如果两者不一样,说明要更新了。返回新的资源,跟常规的HTTP请求响应的流程一样。 否则返回304,告诉浏览器直接用缓存。

  • 两者对比

1、在精准度上,ETag优于Last-Modified。优于 ETag 是按照内容给资源上标识,因此能准确感知资源的变化。而 Last-Modified 就不一样了,它在一些特殊的情况并不能准确感知资源变化,主要有两种情况:

(1)编辑了资源文件,但是文件内容并没有更改,这样也会造成缓存失效。

(2)Last-Modified 能够感知的单位时间是秒,如果文件在 1 秒内改变了多次,那么这时候的 Last-Modified 并没有体现出修改了。

2、在性能上,Last-Modified优于ETag,也很简单理解,Last-Modified仅仅只是记录一个时间点,而 Etag需要根据文件的具体内容生成哈希值。

如果两种方式都支持的话,服务器会优先考虑ETag。

3、网络请求

TCP三次握手

HTTP是基于TCP/IP协议通信协议来传递数据的,主要是客户端和服务器端之间的通信格式,不涉及数据包传输。

先说个核心思想,TCP的三次握手需确认客户端和服务端的两项能力: 发送的能力和接收的能力

以下是TCP三次握手的流程图

  • 最开始双方都处于CLOSED状态。
  • 然后服务端开始监听某个端口,进入了LISTEN状态。
  • 然后客户端主动发起连接,发送 SYN (同步序列编号Synchronize Sequence Numbers), 自己变成了SYN-SENT状态。(第一次)
  • 服务端接收到之后,返回SYNACK(Acknowledge character确认字符 对应客户端发来的SYN),自己变成了SYN-RCVD。(第二次)
  • 之后客户端再发送ACK给服务端,自己变成了ESTABLISHED状态;服务端收到ACK之后,也变成了ESTABLISHED状态。(第三次次)

为什么不是两次?

根本原因: 无法确认客户端的接收能力。

如果是两次,客户端现在发了SYN 报文想握手,但是这个包滞留在了当前的网络中迟迟没有到达,TCP 以为这是丢了包,于是重传,两次握手建立好了连接。 看似没有问题,但是连接关闭后,如果这个滞留在网路中的包到达了服务端呢?这时候由于是两次握手,服务端只要接收到然后发送相应的数据包,就默认建立连接,但是现在客户端已经断开了。

看到问题的吧,这就带来了连接资源的浪费。

为什么不是四次或者更多?

三次握手的目的是确认双方发送和接收的能力,那四次握手可以嘛?

当然可以,100 次都可以。但为了解决问题,三次就足够了,再多用处就不大了。

三次握手过程中可以携带数据么?

第三次握手的时候,可以携带。前两次握手不能携带数据。

如果前两次握手能够携带数据,那么一旦有人想攻击服务器,那么他只需要在第一次握手中的 SYN 报文中放大量数据,那么服务器势必会消耗更多的时间和内存空间去处理这些数据,增大了服务器被攻击的风险。

第三次握手的时候,客户端已经处于ESTABLISHED状态,并且已经能够确认服务器的接收、发送能力正常,这个时候相对安全了,可以携带数据。

3、浏览器的解析和渲染

4、重绘和回流

浏览器渲染过程:

回流(重排)

  • 触发条件

简单来说,就是当我们对 DOM 结构的修改引发 DOM 几何尺寸变化的时候,会发生回流的过程。

以下操作会触发回流:

1、一个 DOM 元素的几何属性变化,常见的几何属性有width、height、padding、margin、left、top、border 等等, 这个很好理解。

2、使 DOM 节点发生增减或者移动。

3、读写 offset族、scroll族和client族属性的时候,浏览器为了获取这些值,需要进行回流操作。

4、调用 window.getComputedStyle 方法。

  • 回流过程

依照上面的渲染过程,触发回流的时候,如果 DOM 结构发生改变,则重新渲染 DOM 树,然后将后面的流程(包括主线程之外的任务)全部走一遍。

相当于将解析和合成的过程重新又走了一篇,开销是非常大的。

重绘

  • 触发条件

当 DOM 的修改导致了样式的变化,并且没有影响几何属性的时候,会导致重绘(repaint)。

  • 重绘过程

由于没有导致 DOM 几何属性的变化,因此元素的位置信息不需要更新,从而省去布局的过程。流程如下:

跳过了生成布局树和建图层树的阶段,直接生成绘制列表,然后继续进行分块、生成位图等后面一系列操作。

重绘不一定导致回流,但回流一定发生了重绘。