浏览器的强制缓存和协商缓存机制

794 阅读6分钟

前言

💯 知其然,更知其所以然,举一反三,融会贯通

浏览器缓存是前端开发中绕不过去的一道坎,日常在开发或者项目部署的时候,可能会遇到各种各样的问题,包括图片,样式,脚本等等... 只要在代码没问题的前提下,大部分情况是因为缓存原因,可以通过Ctrl+F5解决,但是为什么会出现这些问题呢,单从前端的角度其实不好理解透彻,更多的在服务器端的配置和浏览器的机制,接下来具体分析一下

缓存方案

关于浏览器缓存,可以分为两类:强制缓存 和 协商缓存

资源缓存的方案没有绝对的,我们需要针对具体的项目资源场景进行配置,通常情况下我们可以两者相结合使用

  • html文档资源配置协商缓存

  • css, js和图片等资源配置强制缓存

浏览器缓存流程

504787d34e1d41379d2d8a3754998810_tplv-k3u1fbpfcp-zoom-in-crop-mark_4536_0_0_0.webp

强制缓存

当浏览器存在缓存并且命中强制缓存时(没有过期),浏览器不会向服务器发起请求,直接读取缓存的数据,在chrome浏览器控制台network选项中可以看到请求资源返回的是200状态码,并且size栏显示的是from disk cache 或者 from memory cache, 如下图所示:

8ec193a24bbb4ef5bb10294b95167f59_tplv-k3u1fbpfcp-zoom-in-crop-mark_4536_0_0_0.webp

那么此时你可能会想到disk和memory两者的区别是什么呢?下面有一个简单的对比

memory cachedisk cache
相同点只能存储一些派生类资源文件只能存储一些派生类资源文件
不同点存储在内存,退出进程时数据会被清除存储在硬盘, 退出进程时数据不会被清除
存储的资源一般脚本,样式,字体,图片会存在内存中一般非脚本会存在磁盘当中(部分css文件)

一般情况下,浏览器会将就js脚步和图片等资源存入内存中,当刷新页面的时候直接从内存读取(from memory cache),而大部分css文件则存入磁盘中,刷新时会从磁盘读取(from disk cache), 具体的缓存规则还有待去研究😆

针对强制缓存可以通过设置两种HTTP头部标识实现:Expires 和 Cache-Control

Exprise

Expirse是HTTP/1.0中的规范,缓存过期时间,用来指定资源的到期时间,它是服务器中一个绝对时间的GMT格式的时间字符串,这个时间代表资源过期时间,只要在这个时间之前请求,即命中缓存。

不过这种方式有一个明显的缺点,由于失效时间是一个绝对时间,当服务器和客户端时间偏差较大时,对于缓存的时效性会存在混乱,目前一般不会使用此缓存机制了

Cache-Control

Cache-Control是HTTP/1.1中出现的头部信息,主要利用该字段的max-age值来进行判断,它是一个相对的时间(单位是秒),例如Cache-Control: max-age=3600, 代表着资源有效期是3600秒(1小时),除了此字段外,Cache-Control还有其他的几个比较常用的字段值:

  • no-store:不使用缓存

  • no-cache:资源会被缓存,但请求时会跳过强制缓存直接去服务器进行协商缓存,验证资源是否过期

  • public:表示响应可以被客户端和代理服务器缓存

  • private:表示响应只可以被客户端缓存

no-storeno-cache是互斥的,publicprivate是互斥的,他们之间不能同时存在

no-cache很容易让人误解,以为是响应不被缓存,实际上no-cache是被缓存的,它是一个协商缓存的标识,只是客户端需要每次都会向服务器发起请求,来验证缓存的有效性,no-store才是不缓存

Cache-Control缓存比Expire的优先级高,一般情况下Expirse只是一种兼容的实现,当某些浏览器不支持Cache-control的情况下,才会使用Expirse

协商缓存

顾名思义,协商缓存是指在使用本地缓存资源之前,需要向服务器发送一次请求,与之协商当前的资源是否已经过期或者发生变化

协商缓存触发条件:

Cache-Control的值为no-cahce 或者 cache-control中max-age的值为0

针对协商缓存可以通过设置两种HTTP头部标识实现:Last-Modified 和 ETag

Last-Modified

Last-Modified是通过文件最后一次修改的时间来判断文件是否发生变化,主要的实现方式是:

第一次请求:

  • 首先在服务器端读出文件的修改时间

  • 将读出来的修改时间设置给响应头的Last-Modified字段,告诉浏览器

  • 最后设置Cache-Control:no-cache

e7e3cbc1a7724ea6b01498540c5e9e8a_tplv-k3u1fbpfcp-zoom-in-crop-mark_4536_0_0_0.webp

第二次请求:

  • 浏览器客户端会在请求头中设置if-modified-since字段,其值正是上一次响应头中Last-Modified的值

  • 服务器通过请求头读取if-modified-since的值,然后再读取资源在服务器的修改时间,这两个值进行对比

  • 如果新的修改时间大于上一次(if-modified-since)的值,说明文件已经被修改,需要返回新的资源,否则返回304,让浏览器使用本地缓存

30aa662bff7242528908a5df71d287eb_tplv-k3u1fbpfcp-zoom-in-crop-mark_4536_0_0_0.webp

Last-Mofified的缺点是如果文件只是进行了编辑(文件存储时间会发生变化),但是内容没有发生任何变化,当请求时,服务器还是会返回新的资源,这样就造成了浪费网络资源

ETag

为了弥补Last-Modified通过修改时间判断的不足,从HTTP/1.1规范开始新增了一个ETag的头信息,即实体标签,其实现主要是服务器会为不同的资源文件进行哈希计算生成一个唯一的标识

第一次请求:

  • 首先在服务器端生成文件的ETag值

  • 将读出来的值设置给响应头的ETag字段,响应给客户端

  • 最后设置Cache-Control:no-cache

b74e1158392a4977823e40f04f086def_tplv-k3u1fbpfcp-zoom-in-crop-mark_4536_0_0_0.webp

第二次请求:

  • 浏览器客户端会在请求头中设置if-None-Match字段,其值正是上一次响应头中ETag的值

  • 服务器通过请求头读取if-None-Match的值,然后再拿到资源文件的ETag,这两个值进行对比

  • 如果传递的ETag与最新读取的ETag不一样,说明文件已经被修改,需要返回新的资源,否则返回304,让浏览器使用本地缓存

f7bc2d50bfc245d2a71a958d21c6000b_tplv-k3u1fbpfcp-zoom-in-crop-mark_4536_0_0_0.webp

在精确度上,Etag要优于Last-Modified,在性能上ETag要逊于Last-Modified