浏览器缓存,也就是客户端缓存,是浏览器在本地磁盘对用户最近请求过的文档进行存储,当访问者再次访问同一页面时,浏览器就可以直接从本地磁盘加载文档。
浏览器缓存既是网页性能优化里面静态资源相关优化的一大利器,也是无数 web 开发人员在工作过程不可避免的一大问题,所以在产品开发的时候我们总是想办法避免缓存产生,而在产品发布之时又在想策略管理缓存提升网页的访问速度。因此,了解缓存与控制缓存就显得非常重要了。
1. 浏览器缓存
浏览器缓存分为两种,分别为 强缓存(也称本地缓存) 和 协商缓存(也称弱缓存)。
对于强缓存和协商缓存,判定过程如下:
- 浏览器加载资源时,先根据
response
的header
中某些字段判断是否命中强缓存,若命中,浏览器直接从缓存中读取资源,不会发送请求到服务器。 - 当强缓存没有命中的时候,浏览器一定会发送一个请求到服务器,此时服务器根据 resquest 的 header 中某些字段判断是否命中协商缓存,若命中,服务器返回请求,但不会返回这个资源的数据,而是告诉客户端可以直接从缓存中加载这个资源。
对于强缓存和协商缓存,共同点在于,若命中,它们都是从客户端缓存中加载资源,而不是从服务器加载资源数据;而不同点在于,强缓存不发请求到服务器,协商缓存会发请求到服务器以咨询缓存是否过期。
普通刷新会启用协商缓存,忽略强缓存。只有在地址栏或收藏夹输入网址、通过链接引用资源等情况下,浏览器才会启用强缓存,这也是为什么有时候我们更新一张图片、一个 js 文件,页面内容依然是旧的,但是直接浏览器访问那个图片或文件,看到的内容却是新的。
2. 浏览器发送请求过程
当浏览器 第一次发送请求 时,本地无缓存,向 web 服务器发送请求,服务器起端响应请求,浏览器端缓存。
在第一次请求时,服务器会将页面最后修改时间通过 Last-Modified 标识由服务器发送给客户端,客户端记录修改时间;服务器还会生成一个 Etag,并发送给客户端。
当浏览器 再次发送请求 时:
根据上图,浏览器在 第一次请求发生后,再次发送请求 时:
- 浏览器请求某一资源时,会先获取该资源缓存中
response
的header
信息,然后根据header
中的Cache-Control
和Expires
来判断是否过期。若没过期则直接从缓存中获取资源信息,包括缓存的header
的信息,所以此次请求不会与服务器进行通信。这里判断是否过期,则是强缓存相关。 - 如果显示已过期,浏览器会向服务器端发送请求,这个请求会携带第一次请求返回的有关缓存的
header
字段信息,比如客户端会通过 If-None-Match 头将先前服务器端发送过来的Etag
发送给服务器,服务会对比这个客户端发过来的 Etag 是否与服务器的相同,若相同,就将If-None-Match
的值设为false
,返回状态 304,客户端继续使用本地缓存,不解析服务器端发回来的数据,若不相同就将If-None-Match
的值设为true
,返回状态为 200,客户端重新机械服务器端返回的数据;客户端还会通过If-Modified-Since
头将先前服务器端发过来的最后修改时间戳发送给服务器,服务器端通过这个时间戳判断客户端的页面是否是最新的,如果不是最新的,则返回最新的内容,如果是最新的,则返回 304 ,客户端继续使用本地缓存。
3. 强缓存
简而言之,强缓存就是在客户端进行验证本地缓存是否可用。
强缓存是利用 http
头中的 Expires
和 Cache-Control
两个字段来控制的,用来表示资源的缓存时间。
强缓存中,普通刷新会忽略它,但不会清除它;而强制刷新,请求会带上 Cache-Control:no-cache
和 Pragma:no-cache
。
Expires
Expires
是 http1.0 的规范,它的值是一个绝对时间的 GMT 格式的时间字符串,该时间代表着这个资源的失效时间,只要发送请求时间是在 Expires 之前,那么本地缓存始终有效,则在缓存中读取数据。因此这种方式有一个明显的缺点,由于失效的时间是一个绝对时间,所以当服务器与客户端时间偏差较大时,就会导致缓存混乱。
如果同时出现 Cache-Control:max-age 和 Expires,那么 max-age 优先级更高。
1 | cache-control:max-age=691200 |
那么表示资源可以被缓存的最长时间为 691200 秒,会优先考虑 max-age。
Cache-Control
Cache-Control
是在 http1.1 中出现的,是通用首部字段,既可用在 request
和 response
。可以利用该字段的 max-age 值来进行判断,它是一个相对时间,例如 Cache-Control:max-age=3600
,代表着资源的有效期是 3600 秒。对于 response
报文,cache-control
除了该字段外,还有下面几个比较常用的设置值:
- no-cache:不使用本地缓存。需要使用缓存协商,先与服务器确认返回的响应是否被更改,如果之前的响应中存在 ETag,那么请求的时候会与服务端验证,如果资源未被更改,则可以避免重新下载。
- no-store:直接禁止浏览器缓存数据,每次用户请求该资源,都会向服务器发送一个请求,每次都会下载完整的资源。
- public:可以被所有的用户缓存,包括终端用户和 CDN 等中间代理服务器。
- private:只能被终端用户的浏览器缓存,不允许 CDN 等中继缓存服务器对其缓存。
协商缓存
简而言之,协商缓存就是想服务器发送请求以咨询本地缓存是否可用。
协商缓存主要涉及两组 header
字段: Etag
和 If-None-Match
、Last-Modified
和 If-Modified-Since
。
Etag 和 If-None-Match
If-None-Match
是 requset
报文的 header
中的一个字段,形如 if-xxx
这种样式的请求首部字段,都可称为条件请求。Etag
是 response
报文中 header
中的一个字段它是一种可将资源以字符串形式做唯一标识的方式,服务器会为每一份资源分配对应的 ETag 值。
其执行的过程如下:
- 当第一次发起 HTTP 请求时,服务器会为相应的资源返回一个 ETag 值。
- 当再次发起同一个请求时,客户端会在
request
的header
中带上If-None-Match
,而它的值就是 Etag 的值。 - 然后服务器会比对这个客服端发送过来的 Etag 是否与服务器的相同:若相同,就将
If-None-Match
的值设为false
,返回状态为304
,并且服务器不返回该资源的数据,客户端继续使用本地缓存,不解析服务器返回的数据;若不相同,就将If-None-Match
的值设为true
,返回状态为 200 ,同时服务器返回该资源的新数据,客户端重新解析服务器返回的数据。
Last-Modify 和 If-Modify-Since
Last-Modify
是 response
报文的 header
中的一个字段,其值为时间,用于标识该资源的最后修改时间。If-Modify-Since
是 request
报文的 header
中的一个字段,其值也为时间。
其执行过程如下:
当第一次发起 HTTP 请求时,服务器
response
的header
中包含Last-Modify
,标明该资源的最后修改时间,如:1
Last-Modify: Thu,31 Dec 2037 23:59:59 GMT
当再次发起 HTTP 请求时,客户端
requset
的header
中包含If-Modify-Since
字段,该值为缓存之前服务器返回的Last-Modify
的值,服务器收到 If-Modify-Since 后,根据资源的最后修改时间判断是否命中缓存。- 如果命中缓存,则返回 304 ,并且不会返回资源内容,并且不会返回 Last-Modify 。
两者比较
Last-Modified
与 Etag
类似。不过 Last-Modified
表示响应资源在服务器最后修改时间而已。与 Etag
相比,不足为:
- Last-Modified 标注的最后修改只能精确到秒级,如果某些文件在 1 秒钟以内,被修改多次的话,它将不能准确标注文件的修改时间;
- 如果某些文件会被定期生成,当有时内容并没有任何变化,但 Last-Modified 却改变了,导致文件没法使用缓存;
- 有可能存在服务器没有准确获取文件修改时间,或者与代理服务器时间不一致等情形。
而且,Etag 是服务器自动生成或者由开发者生成的对应资源在服务器端的唯一标识符,能够更加准确的控制缓存。
另外,Last-Modified 与 ETag 是可以一起使用的, 服务器会优先验证 ETag,一致的情况下,才会继续比对 Last-Modified,最后才决定是否返回 304。
参考资料
浏览器文件缓存优化策略
HTTP 强缓存和协商缓存
http 协商缓存 VS 强缓存
浅析 HTTP 缓存的机制 - 浏览器缓存
HTTP 缓存机制