你有没有发现,经常访问的网页第二次打开速度会快很多?这背后藏着HTTP协议的性能优化神器——304 Not Modified状态码。今天我们就来拆解这个"缓存通行证"的工作原理,以及如何在项目中正确应用。
一、304状态码不是错误,是性能优化信号
很多开发者第一次看到304状态码会以为是"报错",其实它是服务器返回的成功响应。当客户端发送带有条件请求头的GET请求时,如果资源自上次获取后没有被修改,服务器就会返回304状态码,告诉客户端:"你缓存里的版本还能用,不用重新下载了"。
核心价值:减少重复数据传输,降低服务器压力,提升页面加载速度。据统计,合理使用304可使静态资源加载时间缩短60%以上。
二、304背后的两大验证机制
服务器判断资源是否修改,依赖两组关键的请求头/响应头组合:
| 验证方式 | 请求头 | 响应头 | 适用场景 |
|---|---|---|---|
| 时间验证 | If-Modified-Since | Last-Modified | 静态文件(图片、CSS、JS) |
| 内容验证 | If-None-Match | ETag | 动态内容(API接口、HTML页面) |
| 工作流程示例: |
-
首次请求:客户端请求
/index.html,服务器返回200状态码,并附带Last-Modified: Wed, 10 Nov 2025 12:00:00 GMT和ETag: "12345-abcde" -
二次请求:客户端再次请求时,会带上
If-Modified-Since: Wed, 10 Nov 2025 12:00:00 GMT和If-None-Match: "12345-abcde" -
服务器验证:如果资源未修改,直接返回304状态码,不携带响应体
三、实战:用Express实现304缓存逻辑
下面通过Node.js+Express演示如何在服务器端配置304响应,关键代码已标注注释:
const express = require('express');
const app = express();
const fs = require('fs');
const path = require('path');
app.get('/resource', (req, res) => {
const filePath = path.join(__dirname, 'resource.txt');
const stats = fs.statSync(filePath);
// 生成缓存标识
const lastModified = stats.mtime.toUTCString(); // 最后修改时间
const etag = `"${stats.size}-${stats.mtime.getTime()}"`; // 基于文件大小和修改时间的ETag
// 验证条件请求头
if (req.headers['if-modified-since'] === lastModified ||
req.headers['if-none-match'] === etag) {
// 资源未修改,返回304
return res.status(304).end();
}
// 首次请求或资源已修改,返回完整内容
res.setHeader('Last-Modified', lastModified);
res.setHeader('ETag', etag);
res.sendFile(filePath);
});
app.listen(3000, () => {
console.log('Server running on http://localhost:3000');
});
运行代码后,可通过Chrome开发者工具的Network面板观察:首次请求返回200,二次请求返回304,且Response Size显著减小。
四、避坑指南:304的3个常见误区
-
误区1:把304当成错误处理
解决方案:在AJAX请求中,需单独处理304响应,直接使用本地缓存数据,而非提示"请求失败" -
误区2:过度依赖ETag导致性能损耗
解决方案:静态资源优先使用Last-Modified,动态内容才用ETag,避免服务器重复计算哈希值 -
误区3:忽略缓存控制头配置
解决方案:配合Cache-Control头(如max-age=3600),减少不必要的条件请求
五、互动练习:你能答对这些问题吗?
选择题:以下哪种情况服务器会返回304状态码?(多选)
-
A. 客户端发送If-Modified-Since,且资源未修改
-
B. 客户端未发送任何条件请求头
-
C. 客户端发送If-None-Match,且ETag匹配
-
D. 资源已被修改,但服务器配置错误