# 从输入网址到页面渲染经历了什么
从用户在地址栏输入网址开始到页面渲染完毕,主要由两部分组成。
- 网络层面
- 浏览器渲染层面
大致可以分成以下步骤:
- 输入网址,回车
- 缓存解析:如果浏览器本地缓存有资源从缓存取资源
- 域名解析:DNS 解析,将域名解析成 IP,如果缓存中存在,直接丛缓存中取 IP,不用做域名解析
- 发送请求:向服务器发送请求
- TCP 连接、三次握手:建立浏览器端和服务器端连接
- 服务器接到请求:服务器响应请求
- 数据传输
- 浏览器端拿到数据,解析 html 文件,构建 DOM 树,CSSOM 树,js 文件的加载可能会阻塞页面的渲染
- 初始的 html 被完全加载和解析后会触发 DOMContentLoaded 事件
- CSSOM 树和 DOM 树构建完成后会开始生成 Render 树,绘制
- 在生成 Render 树的过程中,浏览器就开始调用 GPU 绘制,合成图层,将内容显示在屏幕上
- 没有文件传输,四次挥手,TCP 连接断开
对这些步骤进行分析,并对其进行优化
# 2.缓存解析
# 2.1 缓存优先级
- Service Worker
- Memory Cache
- Disk Cache
- Push Cache
- 网络请求
# 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.浏览器端拿到数据
懒加载