浅谈浏览器缓存机制及不同刷新行为对它的影响

3,655 阅读8分钟

测试同学:你问题改了嘛?我怎么没看到效果啊?

前端同学:我改了啊,可能有缓存吧,清一下缓存试试(内心os:难道是我写的代码有问题?🤔🤔🤔)。

首先说下为什么需要缓存?

现在的前端页面已经不是刀耕火种时代的效果了,随便打开一个页面都能看到各种炫酷的交互效果。像现在各种电商的促销活动:618,双11,双12🛒🛒🛒。每一个活动对于服务器都是一个巨大的挑战,就拿一个图片来说,如果每个用户每次访问这个页面都要去服务器拿新的资源的话,服务器应该早就崩溃了😵😵😵。

我们主要来讨论下浏览器的缓存机制,它同时也是前端面试中必问的一道题。

浏览器的缓存机制

浏览器的缓存主要是遵循http缓存机制,主要分为两种缓存:

  • 强缓存
  • 协商缓存(也叫对比缓存)

强缓存

强缓存包括:Pragma,Expires,Cache-Control

首先解释下什么是强缓存

强缓存是代表浏览器进行一次请求后,把请求的文件放在内存或者硬盘中,有的时候我们会在状态码中看到from memory cache或者from disk cache,这时候其实代表我们命中了强缓存,此时并没有发生浏览器发送请求的行为。

memory cache 跟disk cache的区别

  • memory cache 是把资源缓存到内存中,当进程退出时,内存中的数据会被清空,下次访问需要执行别的缓存策略(一个浏览器的标签页相当于一个进程)
  • disk cache 是把资源缓存在磁盘中,进程退出时不受影响,下次访问可以继续执行此次缓存策略

Pragma

现代浏览器一般都是基于HTTP/1.1的。 由于 Pragma 在 HTTP 响应中的行为没有确切规范,所以不能可靠替代 HTTP/1.1 中通用首部 Cache-Control,尽管在请求中,假如 Cache-Control 不存在的话,它的行为与 Cache-Control: no-cache 一致,建议只在需要兼容 HTTP/1.0 客户端的场合下应用 Pragma 首部。

Pragma: no-cache

作用是强制要求缓存服务器在返回缓存的版本之前将请求提交到源头服务器进行验证。

Expires

先来看下它的设置参数:

expires:10s/10m/10h/10d; //分别对应:秒,分,时,天

Expires的值为服务器返回的规定该资源的到期时间,在下一次浏览器请求时会拿本地时间对比这个服务器返回的时间,如果没有过期则直接可以使用缓存数据,无需再发起请求。

这时候问题来了,本地的时间是可以修改的

一方面对比的是本地的时间会有一定的误差;另一方面现在浏览器基本都是基于http1.1版本的,Expires是http1.0版本的产物,这个时候就出现了它的替代品Cache-Control。

Cache-Control

我们先来看下它的常用参数都有哪些:

参数 含义
max-age 单位是秒,失效的时间
no-cache 强制要求缓存把请求提交给原始服务器进行验证
no-store 缓存不应存储有关客户端请求或服务器响应的任何内容
public 存在响应头中,表示可以被任何对象缓存
private 存在响应头中,表示只能被单个用户缓存,不能作为共享缓存

指令不区分大小写,多个指令以逗号分隔,Cache-Control比Expires的优先级要高

no-cache跟no-store傻傻分不清楚

首先cache-control是一个通用消息头字段,就是说请求头跟响应头都可以加入这个字段,但是在不同的位置意思是不同的:

  • 请求头中的no-cache代表每次都去请求新的资源,跳过所有类型的缓存。
  • 响应头中的no-cache代表每次都要去服务器验证缓存是否过期,跳过强缓存。
  • no-store代表禁用一切缓存。
  • 另外说一下max-age=0,代表每次都要去服务器验证缓存是否过期,跳过强缓存。

协商缓存(对比缓存)

协商缓存包括:Last-Modified/If-Modified-Since,Etag/If-None-Match

什么叫协商缓存?

当浏览器检查强缓存过期后,于是发送请求到服务器,这时候服务器并不会直接去拿新的资源,而是要检查请求头部是否有缓存标识If-Modified-Since或者If-None-Match,判断成功后则返回304 Not Modified,通知客户端可以使用缓存的数据。

对于协商缓存,在相对高的nginx版本里面,会自动给客户端返回Last-Modified跟Etag。客户端在下次请求同样的url时,根据HTTP协议的规定,会带上If-Modified-Since报头,值为上次服务器返回的Last-Modified值;同时也会带一个If-None-Match报头,值为上次服务器返回的Etag值。我们可以在Response Headers 跟 Request Headers 中查看参数。

Last-Modified/If-Modified-Since

Last-Modified是存在于响应头部,包含源头服务器认定的资源做出修改的日期及时间。它通常被用作一个验证器来判断接收到的或者存储的资源是否彼此一致。相对应的If-Modified-Since存在于请求头部,它的值是上一次服务器返回的Last-Modified的值。

请求头:

响应头:

Etag/If-None-Match

Etag存在于响应头部的,是服务器资源的唯一标识符,相当于文件指纹,如果文件改变则这个值就会改变。相对应的If-Modified-Since存在于请求头部,它的值是上一次服务器返回的Etag的值。

请求头:

响应头:

有Last-Modified为什么还需要Etag?

  • Last-Modified精确度只到的级别,如果遇到短时间内频繁改动的资源修改操作则失效
  • 有时候可能会出现某些文件会被定时生成,其实内容都是一样的,但是最后修改时间却改变了,则命中缓存失效
  • 可能存在服务器不能获取准确的文件修改时间的情况,集群服务器上各个服务器上的文件时间可能不同

用户的刷新行为对缓存的影响

此处描述的是mac下的chrome,针对具体资源去访问的结果,每个版本的浏览器可能存在不同。

行为一:

浏览器新开窗口的输入url进行访问,先检查强缓存,如果命中Status Code会显示 200 OK (from disk cache)或者200 OK (from memory cache)

行为二:

地址回车或者正常重新加载(command + r)会导致浏览器跳过检查强缓存,直接去服务器检查协商缓存,如果命中则Status Code会显示304 Not Modified

行为三:

硬性的重新加载行为(command + shift + r)会导致浏览器跳过强缓存跟协商缓存,直接去请求新的资源文件,返回状态码200。

行为四:

清空缓存并硬性重新加载。会导致浏览器把缓存清空掉,直接去请求资源,返回状态码200。

如果是对一个站点的访问,页面里面包括静态资源,则对这种静态资源来说上述行为一跟行为二都先检查强缓存再去检查协商缓存,其他行为表现一致。

  • 对单个资源的访问:网上随便找一张图片地址放在浏览器url里面进行访问测试。
  • 对站点的访问:随便访问一个有静态资源的网站进行访问测试。

说一下控制台中的Network选项下面的Disable cache选项

我们都知道勾选中之后起到不使用缓存的效果,但是其实这个时候,并不是把缓存删掉了,而是选择让浏览器跳过所有的缓存策略,这时候浏览器会在请求头上加一个参数:Cache-Control:no-cache。如果把这个选项再放开的话还是会应用缓存策略的,只不过针对具体资源的请求时,浏览器会选择在请求头部加上Cache-Control:max-age=0来跳过强缓存,检查协商缓存。

另外说一下判断浏览器有没有发送请求的方法,可以看下控制台的Waterfall部分:

图中标红的request sent指的是发送HTTP请求的时间,如果值为零或者是没有这个字段,则代表没有发送请求。

有时间再讲解一下其他字段的意思吧,谢谢!!⛽️⛽️⛽️