import{_ as e,o as a,c as s,a as l}from"./app.ab8d0b9f.js";const f=JSON.parse('{"title":"深入理解浏览器缓存机制","description":"","frontmatter":{},"headers":[{"level":2,"title":"强制缓存","slug":"强制缓存","link":"#强制缓存","children":[{"level":3,"title":"什么是强制缓存","slug":"什么是强制缓存","link":"#什么是强制缓存","children":[]},{"level":3,"title":"强制缓存的规则","slug":"强制缓存的规则","link":"#强制缓存的规则","children":[]},{"level":3,"title":"Expires与Cache-Control","slug":"expires与cache-control","link":"#expires与cache-control","children":[]}]},{"level":2,"title":"协商缓存","slug":"协商缓存","link":"#协商缓存","children":[{"level":3,"title":"协商缓存的规则","slug":"协商缓存的规则","link":"#协商缓存的规则","children":[]}]},{"level":2,"title":"内存缓存与硬盘缓存","slug":"内存缓存与硬盘缓存","link":"#内存缓存与硬盘缓存","children":[]},{"level":2,"title":"用户对浏览器缓存的控制","slug":"用户对浏览器缓存的控制","link":"#用户对浏览器缓存的控制","children":[]},{"level":2,"title":"参考资料","slug":"参考资料","link":"#参考资料","children":[]}],"relativePath":"article/深入理解浏览器缓存机制.md","lastUpdated":1681575540000}'),n={name:"article/深入理解浏览器缓存机制.md"},o=l(`
浏览器有两种缓存规则:强制缓存与协商缓存
浏览器在服务器发起真正请求前,先检查浏览器缓存:
服务器通过向响应头添加Expires
和Cache-Control
字段来标识强制缓存的状态,浏览器会将这两个信息缓存到本地,后续有相同请求时,优先到浏览器缓存中检查资源是否到期。
其中Cache-Control
优先级比Expires
高,即:二者同时存在时,浏览器以Cache-Control
为标准,检查缓存资源是否过期
Expires
表示当前资源的失效时间,它的值是一个HTTP-日期时间戳,例如:Expires: Thu, 01 Dec 1994 16:00:00 GMT
使用Expires
存在一些弊端:
Expires
是HTTP/1.0
的字段,但是现在浏览器默认使用的是HTTP/1.1
在某些不支持HTTP1.1的环境下,Expires
就会发挥用处
所以Expires
其实是过时的产物,现阶段它的存在只是一种兼容性的写法
如果在Cache-Control
响应头设置了"max-age"或者"s-max-age"指令,那么Expires
头会被忽略
设置Cache-Control
的值有以下规则:
常用的指令:
Cache-Control
的默认取值举几个例子:
此次请求之后的600秒内,如果浏览器再次发起请求,那么直接使用缓存中的资源:
Cache-Control: max-age=600
浏览器可以缓存资源,但每次使用缓存资源前都必须重新验证其有效性:
Cache-Control: no-cache
Cache-Control: max-age=0, must-revalidate
这意味着每次都会发起 HTTP 请求,但当缓存内容仍有效时可以跳过 HTTP 响应体的下载
当浏览器检查本地的强制缓存已经失效后,浏览器携带该资源的协商缓存标识向服务器发起请求,由服务器根据缓存标识决定是否继续使用本地缓存。
服务器与浏览器通过两两成对的请求头来控制协商缓存:
Etag
If-None-Match
Last-Modified
If-Modified-Since
其中,Etag
与Last-Modified
是由服务器设置的响应头的字段,If-None-Match
与If-Modified-Since
则是浏览器向服务器发送的请求头的字段
Etag
是上一次加载资源时,服务器返回的ResponseHeader,是对该资源的一种唯一标识,只要资源有变化,Etag
就会重新生成。浏览器在下一次加载资源向服务器发送请求时,会将上一次返回的Etag
值放到RequestHeader里的If-None-Match
里,服务器接受到If-None-Match
的值后,会拿来跟该资源文件的Etag
值做比较,如果相同,则表示资源文件没有发生改变,命中协商缓存。
Etag
由服务器生成,标志当前资源的唯一标识,一般包含大小、修改时间等信息If-None-Match
浏览器缓存到本地的Etag
值HTTP协议并未规定Etag
的内容是如何生成的,但一般包含大小、修改时间等信息
Node.js下生成Etag
的示例:
// 根据文件的fs.Stats信息计算出etag
const genEtag = (stat) => {
const fileLength = stat.size // 文件的大小
const fileLastModifiedTime = stat.mtime.getTime() // 文件的最后更改时间
// 数字都用16进制表示
return \`\${fileLength.toString(16)}-\${fileLastModifiedTime.toString(16)}\`
}
Last-Modified
是该资源文件最后一次更改时间,服务器会在ResponseHeader里返回,同时浏览器会将这个值保存起来,在下一次发送请求时,放到RequestHeader里的If-Modified-Since
里,服务器在接收到后也会做比对,如果相同则命中协商缓存。
Last-Modified
由服务器添加,标志资源文件上次被修改的时间If-Modified-Since
浏览器缓存到本地的Last-Modified
值If-None-Match
的优先级要高于If-Modified-Since
,即:如果浏览器同时存在
Etag
要优于Last-Modified
。Last-Modified
的时间单位是秒,如果某个文件在1秒内改变了多次,那么他们的Last-Modified
其实并没有体现出来修改,但是Etag每次都会改变确保了精度;如果是负载均衡的服务器,各个服务器生成的Last-Modified
也有可能不一致。Etag
要逊于Last-Modified
,毕竟Last-Modified
只需要记录时间,而Etag
需要服务器通过算法来计算出一个值。Etag
。当我们打开一个新网页,服务器返回200,将资源发送给浏览器,浏览器做本地缓存
当我们刷新标签页,浏览器从内存缓存获得资源
当我们关闭标签页重新打开,浏览器从硬盘缓存获得资源
在浏览器中,浏览器会在js和图片等文件解析执行后直接存入内存缓存中,那么当刷新页面时只需直接从内存缓存中读取(MemoryCache);而css文件则会存入硬盘文件中,所以每次渲染页面都需要从硬盘读取缓存(DiskCache)。