# 从输入网址到页面渲染经历了什么

从用户在地址栏输入网址开始到页面渲染完毕,主要由两部分组成。

  1. 网络层面
  2. 浏览器渲染层面

大致可以分成以下步骤:

  1. 输入网址,回车
  2. 缓存解析:如果浏览器本地缓存有资源从缓存取资源
  3. 域名解析:DNS 解析,将域名解析成 IP,如果缓存中存在,直接丛缓存中取 IP,不用做域名解析
  4. 发送请求:向服务器发送请求
  5. TCP 连接、三次握手:建立浏览器端和服务器端连接
  6. 服务器接到请求:服务器响应请求
  7. 数据传输
  8. 浏览器端拿到数据,解析 html 文件,构建 DOM 树,CSSOM 树,js 文件的加载可能会阻塞页面的渲染
  9. 初始的 html 被完全加载和解析后会触发 DOMContentLoaded 事件
  10. CSSOM 树和 DOM 树构建完成后会开始生成 Render 树,绘制
  11. 在生成 Render 树的过程中,浏览器就开始调用 GPU 绘制,合成图层,将内容显示在屏幕上
  12. 没有文件传输,四次挥手,TCP 连接断开

对这些步骤进行分析,并对其进行优化

# 2.缓存解析

# 2.1 缓存优先级

  1. Service Worker
  2. Memory Cache
  3. Disk Cache
  4. Push Cache
  5. 网络请求

# Memory Cache

Memory Cache,内存中的缓存,读取内存中的数据肯定比磁盘快。但是内存缓存虽然读取高效,可是缓存持续性很短,会随着进程的释放而释放。 一旦我们关闭 Tab 页面,内存中的缓存也就被释放了。

# Disk Cache

Disk Cache 也就是存储在硬盘中的缓存,读取速度慢点,但是什么都能存储到磁盘中,比之 Memory Cache 胜在容量和存储时效性上。

在所有浏览器缓存中,Disk Cache 覆盖面基本是最大的。它会根据 HTTP Herder 中的字段判断哪些资源需要缓存,哪些资源可以不请求直接使用,哪些资源已经过期需要重新请求。并且即使在跨站点的情况下,相同地址的资源一旦被硬盘缓存下来,就不会再次去请求数据。

# Push Cache

Push Cache 是 HTTP/2 中的内容,当以上三种缓存都没有命中时,它才会被使用。并且缓存时间也很短暂,只在会话(Session)中存在,一旦会话结束就被释放。

# 网络请求

如果所有缓存都没有命中的话,那么只能发起请求来获取资源了。

# 2.2 缓存策略/机制

# 强缓存

强缓存若命中则直接从缓存中获取资源,不会再与服务端发生通信。

强缓存可以通过设置两种 HTTP Header 实现:Expires 和 Cache-Control 。强缓存表示在缓存期间不需要请求,state code 为 200。

# Expires

Expires 是一个时间戳,即缓存过期时间,但受限于本地时间

# Cache-Control

Cache-Control 可以视作是 expires 的完全替代方案。在当下的前端实践里,我们继续使用 expires 的唯一目的就是向下兼容。

# max-age

Cache-Control 不仅可以设置缓存过期时间

# public 与 private

public 与 private 可以设置资源是否能够被代理服务缓存。

# no-store/no-cache

no-cache 绕开了浏览器:我们为资源设置了 no-cache 后,每一次发起请求都不会再去询问浏览器的缓存情况,而是直接向服务端去确认该资源是否过期(即协商缓存)

no-store 比较绝情,顾名思义就是不使用任何缓存策略。在 no-cache 的基础上,它连服务端的缓存确认也绕开了,只允许你直接向服务端发送请求、并下载完整的响应。

# 协商缓存

协商缓存依赖于服务端与浏览器之间的通信。

协商缓存机制下,浏览器需要向服务器去询问缓存的相关信息,进而判断是重新发起请求、下载完整的响应,还是从本地获取缓存的资源。

如果服务端提示缓存资源未改动(Not Modified),资源会被重定向到浏览器缓存,这种情况下网络请求对应的状态码是 304

# Last-Modified

Last-Modified 是一个时间戳,如果我们启用了协商缓存,它会在首次请求时随着 Response Headers 返回 Last-Modified: Fri, 27 Oct 2017 06:35:57 GMT

随后我们每次请求时,会带上一个叫 If-Modified-Since 的时间戳字段,它的值正是上一次 response 返回给它的 last-modified 值:If-Modified-Since: Fri, 27 Oct 2017 06:35:57 GMT

服务器接收到这个时间戳后,会比对该时间戳和资源在服务器上的最后修改时间是否一致,从而判断资源是否发生了变化。如果发生了变化,就会返回一个完整的响应内容,并在 Response Headers 中添加新的 Last-Modified 值;否则,返回如上图的 304 响应,Response Headers 不会再添加 Last-Modified 字段。

Last-Modified 存在一些弊端: 我们编辑了文件,但文件的内容没有改变。If-Modified-Since 只能检查到以秒为最小计量单位的时间差,当我们修改文件的速度过快时,感知不到文件的改动

# Etag

Etag 是由服务器为每个资源生成的唯一的标识字符串,这个标识字符串是基于文件内容编码的,只要文件内容不同,它们对应的 Etag 就是不同的,反之亦然。因此 Etag 能够精准地感知文件的变化。

Etag 和 Last-Modified 类似,当首次请求时,我们会在响应头里获取到一个最初的标识符字符串,它可以是这样的 ETag: W/"2a3b-1602480f459"

那么下一次请求时,请求头里就会带上一个值相同的、名为 if-None-Match 的字符串供服务端比对了:If-None-Match: W/"2a3b-1602480f459"

Etag 的生成过程需要服务器额外付出开销,会影响服务端的性能,这是它的弊端。Etag 并不能替代 Last-Modified,它只能作为 Last-Modified 的补充和强化存在。 Etag 在感知文件变化上比 Last-Modified 更加准确,优先级也更高。当 Etag 和 Last-Modified 同时存在时,以 Etag 为准。

# 3.域名解析

DNS 解析也是需要时间的,可以通过预解析的方式来预先获得域名所对应的 IP。

比如 quicklink,prefetch,preload

# 4.发送请求

多个请求一起发出去,Promise.all()

使用防抖节流限制发送请求的次数

减少 cookie 或者不用

使用 cdn 能够更快拿到响应内容,可以把静态资源放在上面,cdn 域名不一样,也可以减少 cookie 的传输。

# 5.TCP 连接、三次握手:建立浏览器端和服务器端连接

# 6.服务器接到请求:服务器响应请求

# 7.数据传输

减少图片的大小

雪碧图

# 8.浏览器端拿到数据

懒加载

# 9.html解析

# 10.render树

# 11.GPU绘制

# 12.TCP断开连接

# 参考

LastEditTime: 2023/2/19 15:38:37