浏览器缓存分强缓存和协商缓存
强缓存
不会向服务器发送请求,从缓存中读取资源,返回的状态码是200 如果响应头有expires、pragma或者cache-control字段,代表这是强缓存
Expires
HTTP/1.0 时间不准Cache-Control
HTTP/1.1Cache-Control
优先级高,覆盖Expires
Cache-Control
设置no-cache
开启协商缓存- 缓存过期之后,不管资源有没有变化,都会重新发起请求,重新获取资源
协商缓存
协商缓存就是强缓存失效后,浏览器携带缓存标识向服务器发送请求,由服务器根据缓存标识来决定是否使用缓存的过程。
- 协商缓存生效,返回304
- 协商缓存失效,返回200和请求结果
- Last-Modifed/If-Modified-Since
- Last-Modified是服务器响应请求时,返回该资源文件在服务器最后被修改的时间。
- If-Modified-Since则是客户端再次发起该请求时,携带上次请求返回的Last-Modified值,通过此字段值告诉服务器该资源上次请求返回的最后被修改时间。服务器收到该请求,发现请求头含有If-Modified-Since字段,则会根据If-Modified-Since的字段值与该资源在服务器的最后被修改时间做对比,若服务器的资源最后被修改时间大于If-Modified-Since的字段值,则重新返回资源,状态码为200;否则则返回304,代表资源无更新,可继续使用缓存文件。
- Etag/If-None-Match
-
Etag是服务器响应请求时,返回当前资源文件的一个唯一标识(由服务器生成)。
-
If-None-Match是客户端再次发起该请求时,携带上次请求返回的唯一标识Etag值,通过此字段值告诉服务器该资源上次请求返回的唯一标识值。服务器收到该请求后,发现该请求头中含有If-None-Match,则会根据If-None-Match的字段值与该资源在服务器的Etag值做对比,一致则返回304,代表资源无更新,继续使用缓存文件;不一致则重新返回资源文件,状态码为200。
-
Last-Modifed缺点:
- 文件不改变内容,仅改变修改时间
- 1s修改多次,Last-Modifed只能精确到秒
-
服务器优先验证Etag,若一致,继续比对Last-Modifed,最后判断是否返回304
-
Etag / If-None-Match优先级高于Last-Modified / If-Modified-Since,同时存在则只有Etag / If-None-Match生效。
-
可以这样设置来使用协商缓存
javascriptctx.set('Cache-Control', 'public, max-age=0'); ctx.set('Last-Modified', xxx); ctx.set('ETag', xxx);
If-None-Match
If-Modified-Since
区别
- 优先查找强缓存,没有命中再查找协商缓存
- 强缓存不会发请求到服务器,协商缓存会发请求,所以服务器文件更新了强缓存无法感知,协商缓存可以感知
刷新浏览器
- 直接输入url地址,检查强制缓存
- f5刷新 检查协商缓存,不检查强制缓存
- ctrl+f5 不检查强制缓存和协商缓存,全部重新请求
分类型使用不同缓存
- html使用协商缓存
- css, image, js使用协商缓存,文件名使用hash
缓存优先级
- Service Worker
- Memory Cache(内存)
- Disk Cache(硬盘)
- Push Cache
请求流程
- 浏览器发送请求前,根据请求头的expires和cache-control判断是否命中(包括是否过期)强缓存策略,如果命中,直接从缓存获取资源,并不会发送请求。如果没有命中,则进入下一步。
- 没有命中强缓存规则,浏览器会发送请求,根据请求头的last-modified和etag判断是否命中协商缓存,如果命中,直接从缓存获取资源。如果没有命中,则进入下一步。
- 如果前两步都没有命中,则直接从服务端获取资源。
实战
使用koa模拟 https://github.com/zzfn/koa-server/blob/main/routes/cache.js
javascriptconst Router = require('koa-router') const router = new Router({prefix: '/cache'}) module.exports = router router.get('/Expired', async ctx => { // ctx.response.lastModified = new Date(); const Expired=new Date(new Date().getTime()+10000).toUTCString() ctx.set('Expires',Expired) ctx.body={ now:new Date().toUTCString(), Expired } }) router.get('/cache-control', async ctx => { // ctx.response.lastModified = new Date(); const Expired=new Date(new Date().getTime()+10000).toUTCString() ctx.set('Cache-Control','max-age=10') ctx.body={ now:new Date().toUTCString(), Expired } }) router.get('/etag', async ctx => { console.log(1) // ctx.response.lastModified = new Date(); const Expired=new Date(new Date().getTime()+10000).toUTCString() ctx.set('Etag','1123') ctx.status = 304; ctx.body={ a:1, now:new Date().toUTCString(), Expired } }) router.get('/Last-Modified', async ctx => { // ctx.response.lastModified = new Date(); const Expired=new Date(new Date().getTime()+10000).toUTCString() ctx.set('Cache-Control','max-age=10') ctx.body={ now:new Date().toUTCString(), Expired } }) module.exports = router