【白话前端】从一个故事说明白“浏览器缓存”
一则小故事
小明常去图书馆借阅英文杂志回家看,由于单词量少,他同时需要借阅一本《英汉词典》;
起初,和图书管理员不熟,每次他都要在图书馆借英文杂志和《英汉词典》,放在书包里背回家;这个过程,暂且将其称为“不缓存”;
后来,小明发现图书管理员竟是
妈妈的好朋友和好邻居
王叔叔,经过相认后,王叔叔对小明说:“你每次都要借阅《英汉词典》,我直接借你一整年,在一年内你可以将它放在家里,不需要每次到图书馆来借阅。”小明听了非常高兴,因为他的书包可以轻上一大截;可以持有《英汉词典》一整年的过程,暂且称为“强缓存”;
再后来,小明发现图书管理员王叔叔经常去家里做客,两人关系也愈发亲密;小明问:“王叔叔,英文杂志的更新总是很不规律,我经常去了图书馆,英文杂志却未更新,我借到的依然是上一期的杂志,有啥办法让我少跑路吗?”
王叔叔笑着说:“这还不简单?每次你准备去借阅之前,先把你手里当前持有的杂志期号(etag)用短信发给我,如果图书馆没有更新,我就给你一个304的暗号,你就还是接着读家里那本;如果有了更新,我给你一个200的暗号,你再来图书馆拿书就行;”这个过程,暂且被称为“协商缓存”。
不缓存
不缓存是最容易理解的缓存策略,也最不容易出错,只要每一次刷新页面都去服务器取数据即可;但同样的,不缓存意味着页面加载速度会更慢;
要设置不缓存也很容易,只需要将资源文件的Response Header中的Cache-Control设为no-store即可;
Cache-Control: no-store
cache-control 属性之一:可缓存性
强缓存
对于已知的几乎不会发生变化的资源,可以通过调整策略,使得浏览器在限定时间内,直接从本地缓存获取,这就是所谓的强缓存;
要配置静态资源的强缓存,通常需要发送的缓存头如下:
Cache-Control:public, max-age=31536000
以下是强缓存常用的两种属性↓;
cache-control 属性之:可缓存性
cache-control 属性之: 到期
协商缓存
其实上面故事里关于协商缓存的描述,有一点是非常不准确的,那就是对于浏览器而言,小明发送给王叔叔的不是所谓的“杂志期号”,而是杂志的散列(hash);而这个hash,自然也是王叔叔(服务器端)告诉小明(客户端)的;
在真实情况下,浏览器的协商缓存要触发,只有两种情况:
1.Cache-Control 的值为 no-cache (不强缓存)
or
2.max-age 过期了 (强缓存,但总有过期的时候)
只有在这两种情况下满足其中至少一种时,才会进入协商缓存的过程;
因此,常规的协商缓存,通常分为以下几步:
step1
浏览器第一次发起请求,request上并没有相应请求头;
(小明第一次去图书馆借书)
step2
服务器第一次返回资源,response上带上了两个属性:
etag: "33a64df"
last-modified: Mon, 12 Dec 2020 12:12:12 GMT
(王叔叔借给小明一本书,并告诉小明这本杂志的编号,以及它的发刊日期)
step3
浏览器第二次发起请求,request上携带了上一次请求返回的内容:
if-none-matched: "33a64df"
if-modified-since: Mon, 12 Dec 2020 12:12:12 GMT
(小明第二次借书,先进行了询问:上一次借我的那本的编号和上一次更改后是否有变动?)
step4
服务器发现资源没有改变,于是返回了304状态码;
浏览器直接在本地读取缓存;
(王叔叔说:还没来新货,你先读着上次借的那本吧)
step5
浏览器第三次发起请求,request上携带了上一次请求返回的内容:
if-none-matched: "33a64df"
if-modified-since: Mon, 12 Dec 2020 12:12:12 GMT
(小明第三次借书,先进行了询问:上一次借我的那本的编号和上一次更改后是否有变动?)
step6
服务器检查之后发现,文件已经发生了变化,于是将新的资源、编号、最后变更时间一起返回给了客户端;并返回了200状态码;
if-none-matched: "sd423dss"
if-modified-since: Mon, 30 Dec 2020 12:12:12 GMT
(王叔叔说:来了来了,最新一期的杂志编号、发刊日期如下,这是杂志本身,也一起给你;)
上面过程展示了一次协商缓存生效的过程;
如何在项目中使用?
正常来说,一个前端单页应用(SPA)的项目结构大概如下:
├─favicon.ico
├─index.html
│
├─css
│ └───app.fb0c6e1c.css
│
├─img
│ └───logo.82b9c7a5.png
│
└─js
├───app.febf7357.js
└───chunk-vendors.5a5a5781.js
从命名上可以发现,文件大概分两类:
- index.html & favicon.ico 都属于固定命名,通常情况下名称不会再发生改变;
- css/js/image/ttf 等文件,则通常会以 {name}.{hash}.{suffix}的方式进行命名;
当文件发生变化时,其命名规则,可天然保证文件hash跟着发生变化,从而保证文件的路径发生变化;
因此,针对以上场景,通常情况下可以按以下方式制定缓存策略;
- index.html 和 favicon.ico 设置为“不缓存”或者“协商缓存”(必要不大);
- 名称中带hash的文件(如css/js/image/ttf),可以直接使用“强缓存”策略;
作者:春哥的梦想是摸鱼
链接:https://juejin.cn/post/7030781324650610695