JavaScript 页面缓存

1.前言:缓存是把双刃剑

在默认情况下,当浏览器重复访问同一个资源(比如 JS、CSS、图片),为了节省带宽、提升加载速度,它会把资源缓存到本地。后续请求不再向服务器拉取,而是直接从磁盘或内存中读取。

这听起来很美好,但也有副作用:当服务器上的资源更新了,用户可能还在用旧版本——这就是我们常说的“缓存没刷新”。

为了解决这个问题,HTTP 协议设计了两种缓存策略:

  • 强制缓存(强缓存):在有效期内,完全不发请求,直接用本地缓存
  • 协商缓存(弱缓存):先发请求,问服务器“还能用吗?”,再决定是否用缓存

这是一级引用关键原则:强制缓存优先级高于协商缓存。只有强制缓存失效(或被禁用),才会进入协商阶段

2.强制缓存:前端说了算

强制缓存的核心思想是:“这段时间内,别问,直接用!”

它的控制权在响应头,由服务器设定,但执行权在浏览器。

方式一:Expires(已过时)通过 Expires 设置一个绝对过期时间(UTC 时间)

// 设置过期时间(1分钟后)
var expires = new Date()
expires.setMinutes(expires.getMinutes() + 1)
// 设置响应头
res.setHeader('Expires', expires.toUTCString())

⚠️ 问题:依赖客户端与服务器时间同步。如果用户电脑时间不准,缓存可能提前失效或长期不更新。现代开发已不推荐使用

方式二:Cache-Control: max-age(推荐)使用相对时间(秒),以客户端本地时间为准,更可靠

// 设置响应头
res.setHeader("Cache-Control","max-age=7200")

方式三:启发式缓存(不推荐依赖),如果响应头既没有 Cache-Control,也没有 Expires,但存在 Last-Modified,浏览器会自己估算一个缓存时间

max-age ≈ (当前时间 - Last-Modified) × 10%

🔥 注意:这是浏览器的“善意猜测”,不同浏览器策略不同,不可控!生产环境务必显式设置 Cache-Control

3.协商缓存:后端说了算

当强制缓存失效(或设置为 no-cache),浏览器会发起请求,但会带上资源标识,让服务器判断:“这个缓存还能用吗?”

方式一:Last-Modified + If-Modified-Since

服务器返回资源最后修改时间:

//获取文件
var data = fs.readFileSync('./www/index.js')
//获取文件时间
var {mtime} = fs.statSync('./www/index.js')
//设置最终修改时间
res.setHeader("last-modified", mtime.toUTCString())
//开启协商缓存
res.setHeader('Cache-Control','no-cache')

下次请求时,浏览器自动带上

服务端比对:

//对比协商缓存的时间,来确定返回结果
var ifModifiedSince = req.headers["if-modified-since"]
if(ifModifiedSince == mtime.toUTCString()){
	//告诉浏览器资源未发生变化,让他读缓存
	res.statusCode = 304
	res.end()
}else{
	//返回图片
	res.end(data)
}

方式二:ETag + If-None-Match(推荐)

ETag 是资源的唯一指纹(通常是内容哈希),下次请求自动带上

//获取文件
var data = fs.readFileSync('./www/index.js')
//hash运算生成字符串
var etagContent = etag(data)
//添加到响应头
res.setHeader('etag', etagContent)
//开启协商缓存
res.setHeader('Cache-Control','no-cache')

服务端比对:

//判断内容是否发生变化
var ifNoneMatch = req.headers["if-none-match"]
if(ifNoneMatch == etagContent){
	//告诉浏览器资源未发生变化,让他读缓存
	res.statusCode = 304
	res.end()
}else{
	//返回图片
	res.end(data)
}

✅ 优势:精准反映内容变化
⚠️ 代价:需计算哈希,有一定 CPU 开销(但现代服务器完全能扛)
🆚 优先级:ETag > Last-Modified

4.生产实践

好消息是:主流服务器和框架早已内置完整的缓存处理逻辑

  • Express:express.static() 自动设置 ETag / Last-Modified,并处理 304
  • Nginx/Apache:静态文件服务默认支持协商缓存
  • CDN(如 Cloudflare):边缘节点直接返回 304,甚至不回源

业务开发者通常只需关注:

  • 静态资源是否启用了长缓存(max-age=31536000)
  • HTML 页面是否设为 no-cache(确保入口最新)
  • 资源是否使用内容哈希命名(如 app.a1b2c3.js),避免缓存污染

动态控制缓存的小技巧:对于路径不变但内容可能变的资源,可通过查询参数绕过缓存

//在资源链接后面添加版本号(v=100),修改通过这个版本号(v=101),强制客户端更新资源
<script src="./index.js?v=100"></script>

5.如何手动清除缓存?

方法一:DevTools → Network → 右键请求 → “Clear browser cache”

页面强制重载(Ctrl+F5 / Cmd+Shift+R)

6.结语

HTTP 缓存不是魔法,而是一套前后端协作的精密机制:

  • 前端(响应头)决定“能不能直接用” → 控制性能
  • 后端(验证逻辑)决定“还能不能用” → 保证一致性

强缓存靠前端,协商缓存靠后端;没有 Cache-Control,浏览器会自己猜。

posted @ 2024-03-22 14:36  ---空白---  阅读(115)  评论(0)    收藏  举报