设置了Cache-Control后移动端图片还是重复请求是怎么回事?
我在开发移动端混合应用时遇到个奇怪的问题。给图片资源设置了响应头Cache-Control: public, max-age=3600,但用Chrome开发者工具模拟移动端时,发现每次打开页面都会重新下载图片,控制台显示200 (from network)。
我已经检查过服务端配置没问题,PC端桌面浏览器能正常命中缓存。尝试过清除浏览器缓存、修改max-age值甚至改用Expires头,移动端依然无效。这是不是移动端特有的缓存机制?还是我的代码哪里写错了?
请求代码如下:
const img = new Image();
img.src = '/assets/logo.png';
document.body.appendChild(img);
// 后续动态加载时
fetch('/assets/logo.png', {
cache: 'force-cache' // 这个设置会不会有问题?
}).then(res => res.blob())
.then(blob => URL.createObjectURL(blob));
移动端调试时发现第二次访问这个图片时,fetch请求依然走网络而不是缓存,难道是fetch和img标签的缓存策略不一致导致的?
首先确认一点:你在响应头设置 Cache-Control: public, max-age=3600 是正确的,PC 能缓存说明服务端没问题。
但移动端模拟器下看到 from network,不一定代表没走缓存。注意区分 Chrome DevTools 中的 "from network" 和 "from disk cache / memory cache"。有时候显示 from network 只是因为刷新触发了重新验证,实际可能是 304 Not Modified,并没有真的下载数据。你可以看下响应状态码是不是 304,size 显示的是 content size 还是 transfer size。
重点是你这段 fetch 代码有问题:
fetch('/assets/logo.png', {
cache: 'force-cache'
})
虽然 force-cache 看起来是“强制用缓存”,但在实际行为中,浏览器可能会为了“确保新鲜度”而发起网络请求去验证缓存是否过期,尤其是当它不确定的时候。更麻烦的是,fetch 默认不会像 img 标签那样自动带上 If-None-Match 或 If-Modified-Since 去做协商缓存,除非你让它走默认策略。
建议改成这样:
fetch('/assets/logo.png').then(res => res.blob())
.then(blob => URL.createObjectURL(blob));
去掉 cache: 'force-cache',让 fetch 使用默认的缓存策略(和浏览器一样),这样它就会遵循你的 Cache-Control 头,命中强缓存时直接读缓存,没过期就不会发请求。
另外,img 标签和 fetch 的缓存空间不完全共享。img 是由浏览器渲染引擎管理的,fetch 是 JavaScript 层面的资源获取,它们的缓存机制独立。所以你用 img 加载一次,fetch 不一定知道。
还有一个可能:你在测试时用了硬刷新(Ctrl+Shift+R 或者模拟器里的清缓存刷新),这会导致所有资源跳过缓存。移动端调试时记得用普通刷新。
最后一个小建议:可以加上 ETag 或 Last-Modified 配合 Cache-Control,让协商缓存生效,这样即使 max-age 过了也能 304 回来,减少流量。
试试把 fetch 的 cache 选项去掉,然后看下图片请求是不是变成 200 (from disk cache) 或者 304 了。应该就能解决问题了。
希望能帮到你!
fetch的地方,它和img标签的缓存策略确实不一样。fetch默认不会完全依赖 HTTP 缓存,即使你设置了cache: 'force-cache',也可能会因为浏览器实现细节导致不生效。特别是移动端浏览器,它们对缓存的处理更激进一点。可以试试这样:把图片加载逻辑改成直接用
Image标签来处理,避免混用fetch和img。如果你非要使用fetch,可以加个额外的缓存验证机制,比如手动检查res.headers.get('Age')来判断是否命中了服务端缓存。另外一个小建议,你的服务端可以再加个
Etag或者Last-Modified头,这样即使fetch不走强缓存,也会通过协商缓存减少不必要的下载。最后附一个改进版代码供参考:
试一下这个方案,应该能解决你遇到的问题。如果还有其他异常表现,可能得看看具体浏览器版本或者网络环境的影响了。