
前端缓存分为浏览器缓存和http缓存,浏览器缓存例如web Storage,cookie等,比较简单这里一笔带过。复杂的是http缓存,分为强缓存与协商缓存两种缓存策略
当然第一次进入一个网站没有缓存的概念,但是根据第一次返回的响应字段,可以得到对应的缓存策略,得益于缓存策略,第二次乃至以后进入网站速度大大提升。
强缓存
对应着这两个字段:HTTP/1.0
时期,使用的是Expires ,而HTTP/1.1
使用的是Cache-Control
Expires :服务端会返回缓存的过期时间点,超过这个时间点缓存失效。但是服务端与浏览器的时间可能不一致,会产生问题,所以被Cache-Control取代
Cache-Control:不是返回时间点而是持续时间
js
Cache-Control:max-age=1000 // (单位是秒)
其下并不只有这一个属性,还有一些其他属性
no-cache:跳过强缓存,走协商缓存
no-store:强,协商缓存都没有
private:用户浏览器会缓存,中间负责转发的代理服务器不能缓存
public:代理服务器能缓存
s-maxage:代理服务器缓存时间
当Expires 和Cache-Control 同时存在的时候,Cache-Control优先级高
如果第二次进入到这个页面,执行缓存策略,第一步就是先判断强缓存,也就是 浏览器
根据返回的强缓存对应字段,判断缓存是否过期,如果没有过期,直接取缓存,不需要再进行协商缓存。

需要补充的是强缓存不需要发送http请求,虽然返回状态码还是200。另外这里的来源是内存/磁盘,并且只要是从内存/磁盘获取的就是走了强缓存。
协商缓存
从 协商
的字面意思上可以看出至少是需要两个人才能协商,也就是这里浏览器还要询问远程服务器缓存是否过期,也有两个字段
Last-Modified :服务器把这个字段返回,下一次浏览器发送http,会在请求头将他带上,只不过字段名改成了If-Modified-Since,接着服务器判断这个时间是否过期,如果返回304代表未过期,否则返回新资源。
ETag :服务器根据文件内容生成的唯一标识 。服务器把这个字段返回,下一次浏览器发送http,会在请求头将他带上,字段名改成了If-None-Match ,服务器接着会跟服务器上该资源的ETag进行比对,如果返回304代表未过期,否则返回新资源。
接着上面的例子如果强缓存判断过期了,就会进入协商缓存,此时会发送http请求,请求头上会加上If-Modified-Since ,If-None-Match ,他们的值就是第一次返回的响应标头中的Last-Modified ,If-Modified-Since。如果返回304代表缓存没过期,否则返回200,服务器返回最新资源。、
总结
来源于内存/磁盘都是强缓存,状态码返回304都是协商缓存,强缓存由浏览器控制,协商缓存由远程服务器控制。
场景
前端:项目已经部署了
测试:还是老样子啊,刷新也没用
前端:强制刷新一下
测试:哦,可以了
以前一直很疑惑这两种刷新有啥区别,跟这里的强缓存,协商缓存又有啥关系。
先通过nginx设置一下服务器缓存策略(强+协商缓存)

打开控制台,右键刷新图标出现三种刷新方式
- 正常重新加载,这里设置了强缓存1000s,可以看到来源都是强缓存,也就是说正常走缓存策略,缓存没过期接着用。
2. 硬性重新加载,使用硬性重新加载后资源的
请求标头
都被加上了 cache-control: no-cache 和 pragma: no-cache
, pragma 则是为了兼容 HTTP/1.0。并且请求头上没有协商缓存对应的字段。也就是不走强缓存也不走协商缓存,服务器返回最新资源

需要注意的是这里的cache-control是在请求标头里,正常应该存在服务器的响应标头里
请求头里的Cache-Control影响的是当前这一次请求,响应头里的Cache-Control是告诉浏览器这样存储,下次依照这样来。影响的是下一次请求。
- 清除缓存并硬性重新加载:先去缓存再硬性重新加载
还有一种资源"始终"命中 memory cache,就是 base64 图片
但是使用硬性加载似乎不是所有资源都不走缓存,可以看到只有第一个是重新请求了,其他都走了强缓存(默认缓存策略)。

可能是html里直接引用的有关,这两个文件发现都是没有走缓存,而其他文件都是正常走缓存策略。

默认缓存策略
现在把nginx里设置的缓存策略都取消,看看 默认缓存策略
可以看到响应头里 没有强缓存
的字段,但是却 有协商缓存
的字段

- 正常重新加载:很奇怪走了强缓存,但是响应头没有强缓存的字段

其实浏览器还存在一个 启发式缓存
的东西,启发式缓存是浏览器缓存的默认行为(即对于没有 Cache-Control
的响应)不是简单的"不缓存",而是根据所谓的"启发式缓存"进行隐式缓存。
HTTP 旨在尽可能多地缓存,因此即使没有给出 Cache-Control
,如果满足某些条件,响应也会被存储和重用。这称为启发式缓存。
通常会根据响应头中的Date
字段(报文创建时间)减去Last-Modified
值的10%作为缓存时间
js
max(0,(Date - Last-Modified)) % 10
- 硬性重新加载:还是html里直接引用的css,js重新拉取最新资源,其他css,js依旧是走默认的缓存策略

因此,没有配置nginx的情况下,默认没有强缓存,但是会触发启发式缓存和协商缓存
现在试试将强缓存取消
js
add_header Cache-Control "no-cache";
正常重新加载:没有进入启发式缓存直接走了协商缓存

硬性重新加载:跟上面一致

使用场景
对于文件名带hash的直接设置强缓存即可,因为如果文件改变文件名也会改变,会被当成一个新资源,另外一些第三方库,静态资源也可以设置久一点的强缓存。 其他不带hash的文件例如index.html就可以使用协商缓存
补充
在 webpack 中有三种生成哈希值规则的方式,可以用来区分文件是否修改。
- hash 与整个项目有关,项目里有文件修改,所有文件的哈希值都会变化。
- chunkhash 与入口有关,同一入口的文件被视为一个整体,当其中一个文件修改时,同入口的所有文件哈希值发生改变。
- contenthash 只与文件内容有关,文件内容发生改变,才会更改该文件的哈希值。