前言
最近有些标题党的嫌疑,但是我还是觉得这些前端相关的重要概念确实应该简洁明了的总结一下,也是对自己学习的负责吧。
概念
缓存,作为前端核心技术中非常重要但是却不那么直观的概念,一直以来其实处于一种边缘状态,有些前端同学甚至认为这完全是一个后端的概念,也有些人仅仅知道304,其他的就随缘了。显然这是不够的。
缓存,又叫cache,首先关于缓存有什么作用大家应该都清楚,毕竟都缓存过视频。就前端来说,缓存往往和HTTP结合比较紧密,简单的来说,缓存实质上就是一种获取到资源的副本,而那么多资源不可能全部缓存到客户端,何况很多资源还是处于不断更新的状态之中,所以我们急需HTTP协议帮助我们管理缓存。所以对于前端来说,HTTP缓存是我们最需要了解的一部分。
所以还有其他缓存么?这就是为什么好多人觉得缓存手段是后端专属的了,缓存广泛存在于web技术的方方面面,典型的如代理服务器缓存,广泛存在于网络之中,同时被用户所使用。同时还有我们熟悉的CDN,这是后话了。
浏览器端强制缓存(Expires)
首先,浏览器对于缓存的处理是根据第一次请求资源时返回的响应头来确定的。
我们随便找个文件,看他的响应头:
Connection: keep-alive
Content-Length: 146
Date: Tue, 14 Jan 2020 08:21:22 GMT
Expires: Tue, 14 Jan 2020 16:23:22 GMT
X-Powered-By: Express
这就引出了我们第一个缓存策略:HTTP/1.0中的定义缓存的字段Expires,当然这个已经过时了,但是我们可以看出这个字段定义了一个时间,指向两分钟后缓存过期,但由于我设置时没有设置本地化,导致实际上是8小时加2分钟后过期,在此期间内我们的数据会在内存中获取,而不会发送请求。
除去这个问题,这种强制缓存的策略不会比较文件有没有更新,从名字就可以看出来,强制缓存嘛。
但是做这个demo的时候发现了一个问题:
这个memory cache到底是什么呢?
Memory Cache和Disk Cache
名字其实不难理解,一个是在内存中获取,一个是在磁盘中获取,引用一位掘金作者的论述:
Chrome会根据本地内存的使用率来决定缓存存放在哪,如果内存使用率很高,放在磁盘里面,内存的使用率很高会暂时放在内存里面。这就可以比较合理的解释了为什么同一个资源有时是from memory cache有时是from disk cache的问题了
想看更多关于缓存位置的,推荐这篇文章。
浏览器端强制缓存(Cache-Control)
针对刚刚说的第一个缺点,Cache-Control进行了修改,这是1.1中的缓存字段。
它定义了相对时间:
Cache-Control: public,max-age=120
Connection: keep-alive
Content-Length: 146
Date: Tue, 14 Jan 2020 08:40:07 GMT
X-Powered-By: Express
但是这无法改变它强制缓存的缺点。
浏览器端协商缓存
这里贴一下demo
/*
* @Date: 2020-01-14 16:03:49
* @LastEditors : Asen Wang
* @LastEditTime : 2020-01-14 16:55:31
* @content: I
*/
const express = require("express");
const app = express();
const PORT = 8888;
const fs = require("fs");
const path = require("path");
const day = require("dayjs");
app.get("/", (req, res) => {
res.send(`<!DOCTYPE html>
<html lang="en">
<head>
<title>Document</title>
</head>
<body>
一文看懂前端应该了解的缓存机制
<script src="/show.js"></script>
</body>
</html>`);
});
app.get("/show.js", (req, res) => {
let jsPath = path.resolve(__dirname, "./static/show.js");
let cont = fs.readFileSync(jsPath);
let status = fs.statSync(jsPath);
let lastModified = status.mtime.toUTCString();
if (lastModified === req.headers["if-modified-since"]) {
res.writeHead(304, "Not Modified");
res.end();
} else {
res.setHeader("Cache-Control", "public,max-age=5");
res.setHeader("Last-Modified", lastModified);
res.writeHead(200, "OK");
res.end(cont);
}
});
app.listen(PORT, () => {
console.log(`listen on ${PORT}`);
});
主要用到了express,之前为了写Expires还用到了day.js。
可以看到,如果我们5秒内没有刷新浏览器,他会直接写:
HTTP/1.1 304 Not Modified
X-Powered-By: Express
Date: Tue, 14 Jan 2020 09:02:16 GMT
Connection: keep-alive
如果5秒内刷新:
Cache-Control: public,max-age=5
Connection: keep-alive
Date: Tue, 14 Jan 2020 09:01:35 GMT
Last-Modified: Tue, 14 Jan 2020 09:01:31 GMT
Transfer-Encoding: chunked
X-Powered-By: Express
同时我们可以在请求头中找到:
If-Modified-Since: Tue, 14 Jan 2020 08:38:36 GMT
这是如果我们修改文件,它会重新发起请求:
HTTP/1.1 200 OK
X-Powered-By: Express
Cache-Control: public,max-age=5
Last-Modified: Tue, 14 Jan 2020 09:05:04 GMT
Date: Tue, 14 Jan 2020 09:05:09 GMT
Connection: keep-alive
Transfer-Encoding: chunked
同时请求中的那个字段也发生了变化:
If-Modified-Since: Tue, 14 Jan 2020 09:01:31 GMT
当然,并不是意味着这样做就完美了,首先GMT,即格林威治标准时间,只能精确到秒,如果一秒内多次修改呢?Etag就是为了解决这个问题而存在的。
Etag
简单的说,它给文件添加了唯一标识,当文件内容改变,标识改变。