什么是浏览器缓存?
浏览器缓存(Brower Caching)是浏览器对之前请求过的文件进行缓存,以便下一次访问时重复使用,节省带宽,提高访问速度,降低服务器压力
http缓存机制主要在http响应头中设定,响应头中相关字段为Expires、Cache-Control、Last-Modified、Etag。
缓存的类别
浏览器缓存分为强缓存和协商缓存
本质区别在于 强缓存是不需要发送HTTP请求的, 而协商缓存需要.也就是在发送HTTP请求之前, 浏览器会先检查一下强缓存, 如果命中直接使用,否则就进入下一步。
强缓存
浏览器不会像服务器发送任何请求,直接从本地缓存中读取文件并返回Status Code: 200 OK
浏览器检查强缓存的方式主要是判断这两个字段:
- HTTP/1.0时期使用的是Expires;
- HTTP/1.1使用的是Cache-Control (expires中文意思有效期, cache-control中文意思缓存管理)
Expires 是一个具体的时间 Cache-Control: max-age=300
表示的是这个资源在响应之后的300s内过期, 也就是5分钟之内再次获取这个资源会直接使用缓存.
Cache-Control:no-store 不缓存
Cache-Control:public 可以被多用户缓存,包括终端和CDN等中级代理服务器
Cache-Control:private 只能被客户端或浏览器缓存
Cache-Control:no-cache no-cache 总是会命中网络,因为在释放浏览器的缓存副本(除非服务器的响应的文件已更新)之前,它必须与服务器重新验证,不过如果服务器响应允许使用缓存副本,网络只会传输文件报头:文件主体可以从缓存中获取,而不必重新下载
Cache-Control: must-revalidate must-revalidate 需要一个关联的 max-age 指令;上文我们把它设置为 10 分钟。如果说 no-cache 会立即向服务器验证,经过允许后才能使用缓存的副本,那么 must-revalidate 更像是一个具有宽期限的 no-cache。情况是这样的,在最初的十分钟浏览器不会向服务器重新验证,但是就在十分钟过去的那一刻,它又到服务器去请求,如果服务器没什么新东西,它会返回 304 并且新的 Cache-Control 报头应用于缓存的文件 —— 我们的十分钟再次开始。如果十分钟后服务器上有了一个新的文件,我们会得到 200 的响应和它的报文,那么本地缓存就会被更新。
Expires 和 Cache-control的对比
- Expires产于HTTP/1.0,Cache-control产于HTTP/1.1;
- Expires设置的是一个具体的时间,Cache-control 可以设置具体时常还有其它的属性;
- 两者同时存在,Cache-control的优先级更高;
- 在不支持HTTP/1.1的环境下,Expires就会发挥作用, 所以先阶段的存在是为了做一些兼容的处理.
若是设置了 Expires , 但是服务器的时间与浏览器的时间不一致的时候(比如你手动修改了本地的时间), 那么就可能会造成缓存失效, 因此这种方式强缓存方式并不是很准确, 它也因此在 HTTP/1.1 中被摒弃了
协商缓存
如果没有命中强缓存,向服务器发送请求,服务器会根据这个请求的request header的一些参数来判断是否命中协商缓存,如果命中,则返回304状态码并带上新的response header通知浏览器从缓存中读取资源;
协商缓存可以通过设置两种 HTTP Header 实现:Last-Modified 和 ETag对比:ETag更精确,性能上Last-Modified好点
- Last-Modified:\ http1.0\ 原理:浏览器第一次访问资源时,服务器会在response头里添加Last-Modified时间点,这个时间点是服务器最后修改文件的时间点,然后浏览器第二次访问资源时,检测到缓存文件里有Last-Modified,就会在请求头里加If-Modified-Since,值为Last-Modified的值,服务器收到头里有If-Modified-Since,就会拿这个值和请求文件的最后修改时间作对比,如果没有变化,就返回304,如果小于了最后修改时间,说明文件有更新,就会返回新的资源,状态码为200
- ETag:\ http1.1\ 原理:与Last-Modified类似,只是Last-Modified返回的是最后修改的时间点,而ETag是每次访问服务器都会返回一个新的token,第二次请求时,该值埋在请求头里的If-None-Match发送给服务器,服务器在比较新旧的token是否一致,一致则返回304通知浏览器使用本地缓存,不一致则返回新的资源,新的ETag,状态码为200
网页获取缓存(三级缓存)
从浏览器发起HTTP请求到获得请求结果, 可以分为以下几个过程:
- 浏览器第一次发起HTTP请求, 在浏览器缓存中没有发现请求的缓存结果和缓存标识
- 因此向服务器发起HTTP请求, 获得该请求的结果还有缓存规则(也就是Last-Modified或者ETag)
- 浏览器把响应内容存入Disk Cache, 把响应内容的引用存入Memory Cache
- 把响应内容存入Service Worker的Cache Storage(如果Service Worker的脚本调用了cache.put())
下一次请求相同资源的时候:
- 调用Service Worker的fetch事件响应
- 查看memory Cache
- 查看disk Cache. 这里细分为:
- 有强缓存且未失效, 则使用强缓存, 不请求服务器, 返回的状态码都是200
- 有强缓存且已失效, 使用协商缓存判断, 是返回304还是200(读取缓存还是重新获取)
LRU 缓存淘汰
LRU(Least recently used,最近最少使用)算法根据数据的历史访问记录来进行淘汰数据,其核心思想是“如果数据最近被访问过,那么将来被访问的几率也更高”。Vue 的 keep-alive 组件中就用到了此算法
整个流程大致为:
- 新加入的数据插入到第一项
- 每当缓存命中(即缓存数据被访问),则将数据提升到第一项
- 当缓存数量满的时候,将最后一项的数据丢弃
class LRUCahe {
constructor(capacity) {
this.cache = new Map();
this.capacity = capacity; // 最大缓存容量
}
get(key) {
if (this.cache.has(key)) {
const temp = this.cache.get(key);
this.cache.delete(key);
this.cache.set(key, temp);
return temp;
}
return undefined;
}
set(key, value) {
if (this.cache.has(key)) {
this.cache.delete(key);
} else if (this.cache.size >= this.capacity) {
this.cache.delete(this.cache.keys().next().value);
}
this.cache.set(key, value);
}
}
// before
const cache = new Map();
// after
const cache = new LRUCahe(50);
Q&A
怎么不使用本地缓存 使用协商缓存
Cache-Control
- no-cache 不使用本地缓存。需要使用协商缓存。
- 可以在客户端存储资源,每次都必须去服务端做新鲜度校验,来决定从服务端获取新的资源(200)还是使用客户端缓存(304)。也就是所谓的协商缓存
怎么禁用本地缓存
- no-store直接禁止浏览器缓存数据,每次请求资源都会向服务器要完整的资源, 类似于 network 中的 disabled cache。永远都不要在客户端存储资源,永远都去原始服务器去获取资源。
用户行为对缓存的影响
F5 刷新的时候,会暂时禁用强缓存 Ctrl + F5 强制刷新的时候,会暂时禁用强缓存和协商缓存
强制不用任何缓存
Cache-control: no-store