一:前言
浏览器缓存 与浏览器储存 是不一样的,友友们不要混淆,关于浏览器储存,具体可以看这篇文章 : 一篇打通浏览器储存
这里大概介绍一下:
cookies | localStorage | sessionStorage | IndexedDB |
---|---|---|---|
服务端设置 | 一直存在 | 页面关闭就消失 | 一直存在 |
4K | 5M | 5M | 无限大 |
自动携带在http请求头中 | 不参与后端 | 不参与后端 | 不参与后端 |
默认不允许跨域,但可以设置 | 可跨域 | 可跨域 | 可跨域 |
二:强缓存
强缓存是指浏览器在请求资源时,如果本地有符合条件的缓存,那么浏览器会直接使用缓存而不会向服务器发送新的请求。这可以通过设置 Cache-Control
或 Expires
响应头来实现。
2.1:Cache-Control 头详解
Cache-Control
是一个非常强大的HTTP头部字段,它包含多个指令,用以控制缓存的行为:
- max-age:指定从响应生成时间开始,该资源可被缓存的最大时间(秒数)。
- s-maxage :类似于
max-age
,但仅对共享缓存(如代理服务器)有效。 - public:表明响应可以被任何缓存存储,即使响应通常是私有的。
- private:表明响应只能被单个用户缓存,不能被共享缓存存储。
- no-cache:强制缓存在使用前必须先验证资源是否仍然新鲜。
- no-store:禁止缓存该响应,每次请求都必须获取最新数据。
- must-revalidate:一旦资源过期,必须重新验证其有效性。
例如,通过设置 Cache-Control: max-age=86400
,可以告诉浏览器这个资源可以在本地缓存24小时。在这段时间内,如果再次访问相同URL,浏览器将直接使用缓存中的副本,而不与服务器通信。
2.2:Expires 头
Expires
是一个较旧的头部字段,用于设定资源过期的具体日期和时间。尽管现在推荐使用 Cache-Control
,但在某些情况下,Expires
仍然是有效的。Expires
的值是一个绝对的时间点,而不是相对时间。例如:
js
Expires: Wed, 09 Oct 2024 18:29:00 GMT
2.3:浏览器默认行为
当用户通过地址栏直接请求资源时,浏览器通常会自动添加 Cache-Control: no-cache
到请求头中。这意味着即使资源已经存在于缓存中,浏览器也会尝试重新验证资源新鲜度,以确保用户看到的是最新的内容。
三:协商缓存
协商缓存发生在资源的缓存条目已过期 或设置了 no-cache
指令的情况下。这时,浏览器会向服务器发送请求,并携带上次请求时收到的一些信息,以便服务器决定是否返回完整响应或只是确认没有更新。
3.1:Last-Modified/If-Modified-Since
后端服务器可以为每个资源设置 Last-Modified
头部,表示资源最后修改的时间。当下一次请求同一资源时,浏览器会在请求头中加入 If-Modified-Since
字段,其值为上次接收到的 Last-Modified
值。服务器检查这个时间戳,如果资源自那以后没有改变,则返回304 Not Modified状态码,指示浏览器使用缓存中的版本。
3.2:ETag/If-None-Match
ETag 提供了一种更精确的方法来检测资源是否发生变化。它是基于文件内容计算出的一个唯一标识符。当客户端请求资源时,服务器会在响应头中提供一个 ETag
值。下次请求时,浏览器会发送 If-None-Match
头部,包含之前接收到的 ETag
。如果资源未改变,服务器同样返回304状态码;如果有变化,则返回完整的资源及新的 ETag
值。
3.3:比较 Last-Modified 和 ETag
虽然 Last-Modified
简单易用,但它基于时间戳,可能会受到时钟同步问题的影响。相比之下,ETag
更加准确,因为它依赖于资源的实际内容。然而,ETag
计算可能需要更多的服务器处理能力。
四:缓存选择
合理的缓存策略能够显著提升网站性能和用户体验。例如,静态资源(如图片、CSS、JavaScript文件)适合设置较长的缓存时间,而动态内容则需谨慎对待,避免缓存不适当的信息。
- 使用工具如 Chrome DevTools 来分析页面加载时间和缓存效果。
- 对不同类型的资源设置合适的
Cache-Control
参数。 - 注意安全性和隐私保护,确保敏感数据不会被错误地缓存。
五:使用示例
- 引入必要的模块 :导入
http
,path
,fs
和mime
模块。 - 创建HTTP服务器 :使用
http.createServer
创建一个HTTP服务器。 - 处理请求 :
- 根据请求的URL生成文件路径。
- 检查文件是否存在。
- 如果是目录,指向该目录下的
index.html
文件。
- 处理协商缓存 :
- 获取请求头中的
If-Modified-Since
字段。 - 比较
If-Modified-Since
与文件的最后修改时间。
- 获取请求头中的
- 读取文件并发送响应 :
- 读取文件内容。
- 设置响应头(包括
Content-Type
,Cache-Control
,Last-Modified
,ETag
)。 - 发送响应体。
- 启动服务器:监听3000端口并启动服务器。
server.js:
js
const http = require('http'); // 引入HTTP模块
const path = require('path'); // 引入路径处理模块
const fs = require('fs'); // 引入文件系统模块
const mime = require('mime'); // 引入MIME类型模块
// 创建一个HTTP服务器
const server = http.createServer((req, res) => {
// console.log(req.url); // /index.html // /assets/image/logo.png
// 根据请求的URL生成文件路径
let filePath = path.resolve(__dirname, path.join('www', req.url));
// 检查文件或目录是否存在
if (fs.existsSync(filePath)) {
const stats = fs.statSync(filePath); // 获取该路径对应的资源状态信息
// console.log(stats);
const isDir = stats.isDirectory(); // 判断是否是文件夹
const { ext } = path.parse(filePath); // 获取文件扩展名
if (isDir) {
// 如果是目录,则指向该目录下的 index.html 文件
filePath = path.join(filePath, 'index.html');
}
// +++++ 获取前端请求头中的if-modified-since
const timeStamp = req.headers['if-modified-since']; // 获取请求头中的 If-Modified-Since 字段
let status = 200; // 默认响应状态码为200
if (timeStamp && Number(timeStamp) === stats.mtimeMs) { // 如果 If-Modified-Since 存在且与文件最后修改时间相同
status = 304; // 设置响应状态码为304,表示资源未变更
}
// 如果不是目录且文件存在
if (!isDir && fs.existsSync(filePath)) {
const content = fs.readFileSync(filePath); // 读取文件内容
res.writeHead(status, {
'Content-type': mime.getType(ext), // 设置 Content-Type 头
'cache-control': 'max-age=86400', // 设置缓存控制为一天
// 'last-modified': stats.mtimeMs, // 资源最新修改时间(可选)
// 'etag': '由文件内容生成的hash' // 文件指纹(可选)
});
res.end(content); // 发送文件内容作为响应体
}
}
});
// 启动服务器,监听3000端口
server.listen(3000, () => {
console.log('listening on port 3000');
});r.listen(3000, () => {
console.log('listening on port 3000');
});
index.html:
js
<body>
<h1>midsummer</h1>
<img src="assets/image/1.png" alt="">
</body>
项目结构如下图,友友们自行准备一张图片,将项目npm init -y
初始化为后端项目,之后下载mime@3 包,在终端输入npx nodemon server.js
运行起来,在浏览器中查看http://localhost:3000/index.html ,观察效果。在检查中的网络里看缓存效果,同时友友们可以更改图片或者缓存方式,体验下不同的浏览器缓存方式