浏览器缓存是一种存储机制,它允许浏览器将网页的部分或全部内容(如HTML文件、图像、JavaScript文件等)存储在用户的本地设备上。这样,当用户再次访问同一个网站时,浏览器可以从缓存中加载这些资源,而不需要重新从服务器下载,从而加快了页面的加载速度并减少了网络流量。
以下是关于浏览器缓存的一些关键概念和技术细节:
类型
-
强制缓存 (
MUST-Cache
):- Expires Header: 指定资源过期的时间点。
- Cache-Control Header: 包含
max-age
指令来指定资源的有效时间。
-
验证缓存 (
Validate Cache
):- 当资源可能已经过期但仍然可能有效时,浏览器会使用条件请求向服务器验证资源是否已更新。
- Last-Modified 和 If-Modified-Since Headers: 用于检查资源是否已有新的修改版本。
- ETag 和 If-None-Match Headers: 使用唯一标识符来确定资源是否改变。
如何工作
浏览器第一次加载资源,服务器返回200,浏览器从服务器下载资源文件,并缓存资源文件和response header,以供下次加载时对比使用;
下次加载资源时,由于强制缓存优先级更高,所以会执行强制缓存策略。
基于Expires字段实现的强缓存
在以前,我们通常会使用响应头的Expires
字段去实现强缓存。该字段的作用是,设定一个强缓存时间。在此时间范围内,则命中强缓存,直接从内存(或磁盘)中读取缓存返回。但是该字段存在问题:过度依赖本地时间。
基于Cache-control实现的强缓存(代替Expires的强缓存实现方法)
字段在http1.1中被增加,Cache-control
完美解决了Expires
本地时间和服务器时间不同步的问题。是当下的项目中实现强缓存的最常规方法。
Cache-control
的使用方法很简单,只要在资源的响应头上写上需要缓存多久就好了,单位是秒。
Cache-Control: max-age=3600
该字段还有其他属性,详情见:https://blog.csdn.net/hyupeng1006/article/details/126599764
如果资源过期,则表明强制缓存没有被命中,则开始执行协商缓存策略。
基于last-modified的协商缓存
基于 Last-Modified
的协商缓存是一种机制,用于确定客户端(通常是浏览器)中的缓存副本是否仍然是最新的。这种机制依赖于服务器端的 Last-Modified
响应头和客户端发出的 If-Modified-Since
请求头。其具体策略是:
1.在第一次请求时,服务器返回响应,并在响应头中包含 Last-Modified
字段,指示该资源最后一次被修改的时间;
2.缓存资源;
3.再次发送请求访问相同资源,浏览器在请求头中包含 If-Modified-Since
字段,其值为上次接收到的 Last-Modified
值;
4.服务器检查资源的最后修改时间是否与 If-Modified-Since
中的时间相同;
- 如果资源没有被修改,则服务器返回一个
304 Not Modified
响应,则客户端直接从缓存中加载资源。 - 如果资源已被修改,则服务器返回一个新的
200 OK
响应,其中包含新的Last-Modified
时间戳以及资源的新内容,同时更新缓存。
const http = require('http');
const fs = require('fs');
const server = http.createServer((req, res) => {
const filePath = 'example.html';
// 获取文件的最后修改时间
fs.stat(filePath, (err, stats) => {
if (err) {
res.writeHead(500);
res.end('Internal Server Error');
return;
}
const lastModifiedTime = stats.mtime.toUTCString();
const ifModifiedSince = req.headers['if-modified-since'];
// 检查是否需要发送304响应
if (ifModifiedSince === lastModifiedTime) {
res.writeHead(304);
res.end();
} else {
// 发送200响应
res.writeHead(200, { 'Last-Modified': lastModifiedTime });
fs.createReadStream(filePath).pipe(res);
}
});
});
server.listen(3000, () => {
console.log('Server running on port 3000');
});
优点
- 减少了不必要的数据传输,提高了性能。
- 降低了服务器负载。
缺点
- 只能精确到秒,如果资源在同一秒内多次修改,可能会导致缓存问题。
- 如果资源经常在短时间内被修改,可能会导致额外的网络请求。
基础ETag的协商缓存
为了克服 Last-Modified
的缺点,通常会结合使用 ETag
。ETag
提供了一个唯一的标识符(文件指纹)来区分资源的不同版本,即使是同一秒内的修改也能被识别出来。
其具体流程与last-modified类似,只不过是将标识符赋给if-None-Match
字段,并进行对比。
const http = require('http');
const fs = require('fs');
const server = http.createServer((req, res) => {
const filePath = 'example.html';
// 获取文件的哈希值作为ETag
fs.readFile(filePath, (err, data) => {
if (err) {
res.writeHead(500);
res.end('Internal Server Error');
return;
}
const etag = `"${Buffer.from(data).toString('base64')}"`; // 使用文件内容的Base64编码作为ETag
const ifNoneMatch = req.headers['if-none-match'];
// 检查是否需要发送304响应
if (ifNoneMatch === etag) {
res.writeHead(304);
res.end();
} else {
// 发送200响应
res.writeHead(200, { 'ETag': etag });
res.end(data);
}
});
});
server.listen(3000, () => {
console.log('Server running on port 3000');
});
总结:
浏览器缓存有许多优势,主要体现在以下几个方面:
-
提高加载速度:
- 浏览器可以直接从缓存加载资源,而无需从服务器获取,这显著加快了页面的加载速度。
-
减少网络流量:
- 由于减少了重复的数据传输,网络流量得到节约,这对于移动网络环境尤为重要。
-
降低服务器负载:
- 服务器不必频繁地处理重复的请求,减轻了服务器的压力。
-
改善用户体验:
- 快速加载的页面让用户感觉更流畅,提升了整体的用户体验。
-
节省带宽成本:
- 对于需要支付带宽费用的网站运营者来说,减少数据传输可以节省成本。
-
离线访问能力:
- 即使在网络连接不稳定或断开的情况下,用户仍然可以访问之前缓存的内容。
-
提高应用性能:
- 对于单页应用(SPA)和 Progressive Web Apps (PWA),合理的缓存策略能够提升应用的响应速度和可用性。
-
减少延迟:
- 通过减少从远程服务器获取数据所需的往返时间,缓存可以降低延迟。
-
支持快速重载:
- 用户刷新页面时,浏览器可以从缓存中快速加载页面,而不是每次都从服务器获取最新内容。