1. 前言
浏览器缓存 是浏览器将用户请求过的静态资源(html,CSS,JS), 存储到电脑本地磁盘中, 当浏览器再次访问时, 就可以直接从本地加载了, 不需要再去服务端请求了.
但也不是说缓存没有缺点, 如果处理不当, 可能会导致服务端代码更新了, 但是用户却还是老页面. 所以前端们要针对项目中各个资源的实际情况, 做出合理的缓存策略.
缓存的优点:
减少了冗余的数据传输, 节省网费
减少服务器的负担, 提升网站性能
加快了客户端加载网页的速度
2. 缓存流程
这里先介绍一下浏览器缓存资源的一个大概的流程.
我们可以认为, 浏览器里有一个专门存放缓存规则的一个数据库, 也可以说是一个映射表, 把缓存资源信息, 同电脑磁盘中的实际文件的地址, 对应起来.(大概意思, 别较真)
而这个缓存规则的表, 在浏览器中是可以看到的: Chrome://cache/
不过我升级了浏览器之后, 就不好使了, 但是找到了 Chrome://net-internals/#httpCache , 不知道是不是就是原来的, 知道的同学也可以反馈一下
浏览器第一次请求资源时
上面所说的 缓存规则, 就是声明所请求的这个资源, 要采取哪种缓存策略? 缓存多长时间? 等等... 而这个规则, 是在 http 的 header 中的返回来的.
注意: 是 response header , 而不是 request header !!!
而实际上, request header 中也会携带规则信息, 下面会讲, 要区分 request 和 response
3. 缓存规则
强缓存和协商缓存.
强缓存
简单粗暴, 如果资源没过期, 就取缓存, 如果过期了, 则请求服务器.
如何判断资源是否过期呢, 也就是说强缓存的规则怎么看?
主要是看 response headers 中的 Cache-Control 的值, 图中的 max-age = 31xxxxxxx, 就是说在这些秒内, 都直接使用缓存, 超过了就继续请求服务器
而和 Cache-Control 并列的, 还有一个 Expires , 已经基本淘汰了, 所以不用管
Cache-Control 的几个取值含义:
private: 仅浏览器可以缓存
public: 浏览器和代理服务器都可以缓存(对于 private 和 public, 前端可以认为一样, 不用深究)
max-age=xxx 过期时间(重要)
no-cache 不进行强缓存(重要)
no-store 不强缓存, 也不协商缓存, 基本不用, 缓存越多才越好呢
注意: 规则可以同时多个
所以, 对于强缓存, 我们主要研究 Cache-Control 中的 max-age 和 no-cache
所以, 判断该资源是否命中强缓存, 就看 response 中 Cache-Control 的值, 如果有 max-age=xxx 秒, 则命中强缓存. 如果 Cache-Control 的值是 no-cache, 说明没命中强缓存, 走协商缓存.
强缓存流程:
所以强缓存步骤已经很清晰了:
第一次请求 a.JS , 缓存表中没该信息, 直接请求后端服务器.
后端服务器返回了 a.JS , 且 http response header 中 cache-control 为 max-age=xxxx, 所以是强缓存规则, 存入缓存表中.
第二次请求 a.JS , 缓存表中是 max-age, 那么命中强缓存, 然后判断是否过期, 如果没过期, 直接读缓存的 a.JS, 如果过期了, 则执行协商缓存的步骤了.
协商缓存
触发条件:
Cache-Control 的值为 no-cache (不强缓存)
或者 max-age 过期了 (强缓存, 但总有过期的时候)
也就是说, 不管怎样, 都可能最后要进行协商缓存(no-store 除外)
这个图, 虽然强缓存命中, 但是也有 ETag 和 Last-Modified , 这两个就是协商缓存的相关规则. 虽然之前的强缓存流程和他俩没关...
ETag: 每个文件有一个, 改动文件了就变了, 可以看似 md5
Last-Modified: 文件的修改时间
也就是说, 每次 http 返回来 response header 中的 ETag 和 Last-Modified, 在下次请求时在 request header 就把这两个带上(但是名字变了 ETag-->If-None-Match,Last-Modified-->If-Modified-Since ), 服务端把你带过来的标识, 资源目前的标识, 进行对比, 然后判断资源是否更改了.
这个过程是循环往复的, 即缓存表在每次请求成功后都会更新规则.
1. 第 n 次请求成功时:
2. 缓存表中更新该资源的 ETag 值
3. 第 n+1 次请求:
从缓存表中取该资源最新的 ETag, 然后加在 request header 中, 注意变名字了, 由 ETag --> If-None-Match
图:
所以协商缓存步骤总结:
请求资源时, 把用户本地该资源的 ETag 同时带到服务端, 服务端和最新资源做对比.
如果资源没更改, 返回 304, 浏览器读取本地缓存.
如果资源有更改, 返回 200, 返回最新的资源.
4. 缓存命中显示
从服务器获取新的资源
命中强缓存, 且资源没过期, 直接读取本地缓存
命中协商缓存, 且资源未更改, 读取本地缓存
注意: 协商缓存无论如果, 都要向服务端发请求的, 只不过, 资源未更改时, 返回的只是 header 信息, 所以 size 很小; 而资源有更改时, 还要返回 body 数据, 所以 size 会大.
7. 其他
0. 怎么配置资源的缓存规则
可以有后端服务器配置, 也可以在 nginx 中配置, 稍后会更新一张 nginx 的配置
1. 为什么要有 Etag
你可能会觉得使用 Last-Modified 已经足以让浏览器知道本地的缓存副本是否足够新, 为什么还需要 Etag 呢? HTTP1.1 中 Etag 的出现 (也就是说, ETag 是新增的, 为了解决之前只有 If-Modified 的缺点) 主要是为了解决几个 Last-Modified 比较难解决的问题:
一些文件也许会周期性的更改, 但是他的内容并不改变(仅仅改变的修改时间), 这个时候我们并不希望客户端认为这个文件被修改了, 而重新 GET;
某些文件修改非常频繁, 比如在秒以下的时间内进行修改,(比方说 1s 内修改了 N 次),If-Modified-Since 能检查到的粒度是 s 级的, 这种修改无法判断(或者说 UNIX 记录 MTIME 只能精确到秒);
某些服务器不能精确的得到文件的最后修改时间.
2. 强缓存与协商缓存的区别可以用下表来表示:
3. 用户行为对缓存的影响
即: F5 会 跳过强缓存规则, 直接走协商缓存;;;Ctrl+F5 , 跳过所有缓存规则, 和第一次请求一样, 重新获取资源
6. 总结
借两个图
来源: https://juejin.im/post/5c417993f265da61285a6075