【白话前端】从一个故事说明白“浏览器缓存”

这是我参与11月更文挑战的第2天,活动详情查看:2021最后一次更文挑战

一则小故事

小明常去图书馆借阅英文杂志回家看,由于单词量少,他同时需要借阅一本《英汉词典》;

起初,和图书管理员不熟,每次他都要在图书馆借英文杂志和《英汉词典》,放在书包里背回家;这个过程,暂且将其称为“不缓存”;

后来,小明发现图书管理员竟是妈妈的好朋友和好邻居王叔叔,经过相认后,王叔叔对小明说:“你每次都要借阅《英汉词典》,我直接借你一整年,在一年内你可以将它放在家里,不需要每次到图书馆来借阅。”小明听了非常高兴,因为他的书包可以轻上一大截;可以持有《英汉词典》一整年的过程,暂且称为“强缓存”;

再后来,小明发现图书管理员王叔叔经常去家里做客,两人关系也愈发亲密;小明问:“王叔叔,英文杂志的更新总是很不规律,我经常去了图书馆,英文杂志却未更新,我借到的依然是上一期的杂志,有啥办法让我少跑路吗?”

王叔叔笑着说:“这还不简单?每次你准备去借阅之前,先把你手里当前持有的杂志期号(etag)用短信发给我,如果图书馆没有更新,我就给你一个304的暗号,你就还是接着读家里那本;如果有了更新,我给你一个200的暗号,你再来图书馆拿书就行;”这个过程,暂且被称为“协商缓存”

逐渐装逼

不缓存

不缓存是最容易理解的缓存策略,也最不容易出错,只要每一次刷新页面都去服务器取数据即可;但同样的,不缓存意味着页面加载速度会更慢

要设置不缓存也很容易,只需要将资源文件的Response Header中的Cache-Control设为no-store即可;

Cache-Control: no-store

cache-control 属性之一:可缓存性

Response Header属性 含义
cache-control no-store 不缓存,这个会让客户端、服务器都不缓存,也就没有所谓的强缓存、协商缓存了。

强缓存

对于已知的几乎不会发生变化的资源,可以通过调整策略,使得浏览器在限定时间内,直接从本地缓存获取,这就是所谓的强缓存;
要配置静态资源的强缓存,通常需要发送的缓存头如下:

Cache-Control:public, max-age=31536000

以下是强缓存常用的两种属性↓;

cache-control 属性之:可缓存性

Response Header属性 含义
cache-control public 表明响应可以被任何对象(包括:发送请求的客户端,代理服务器,等等)缓存,即使是通常不可缓存的内容。(例如:1.该响应没有max-age指令或Expires消息头;2. 该响应对应的请求方法是 POST 。)
cache-control private 表明响应只能被单个用户缓存,不能作为共享缓存(即代理服务器不能缓存它)。私有缓存可以缓存响应内容,比如:对应用户的本地浏览器。

cache-control 属性之: 到期

Response Header属性 含义
cache-control max-age=<1000> 设置缓存存储的最大周期,超过这个时间缓存被认为过期(单位秒)。与Expires相反,时间是相对于请求的时间。

协商缓存

其实上面故事里关于协商缓存的描述,有一点是非常不准确的,那就是对于浏览器而言,小明发送给王叔叔的不是所谓的“杂志期号”,而是杂志的散列(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
复制代码

从命名上可以发现,文件大概分两类:

  1. index.html & favicon.ico 都属于固定命名,通常情况下名称不会再发生改变;
  2. css/js/image/ttf 等文件,则通常会以 {name}.{hash}.{suffix}的方式进行命名;

name-with-hash.png

当文件发生变化时,其命名规则,可天然保证文件hash跟着发生变化,从而保证文件的路径发生变化;

因此,针对以上场景,通常情况下可以按以下方式制定缓存策略

  1. index.html 和 favicon.ico 设置为“不缓存”或者“协商缓存”(必要不大);
  2. 名称中带hash的文件(如css/js/image/ttf),可以直接使用“强缓存”策略

猜你喜欢

转载自juejin.im/post/7030781324650610695