模块缓存与http缓存完全独立:前者是js引擎以绝对url为键的内存module map,决定是否重新执行;后者由http头控制,仅决定是否重新下载。

模块缓存和HTTP缓存是两套完全独立的机制
浏览器对 type="module" 脚本的缓存,不是传统意义上的“HTTP 缓存”,而是 JavaScript 引擎维护的 Module Map:以**完全解析后的绝对 URL** 为键、模块记录为值的内存映射表。它不读取 Cache-Control、ETag 或 Expires,哪怕服务器返回 Cache-Control: no-cache,模块依然被缓存且只执行一次。
这意味着:
- HTTP 缓存决定“是否重新下载文件”,模块缓存决定“下载后是否重新执行”
- 即使文件内容已更新,只要 URL 不变,
import仍返回旧的导出绑定,顶层代码绝不再运行 - 你不能靠禁用 HTTP 缓存来“刷新模块逻辑”,那只会重复加载同一份缓存模块记录
真正影响模块缓存命中率的是 URL 稳定性
模块缓存是否复用,只看两次 import 的 URL 字符串是否完全一致(协议、域名、路径、查询参数)。哪怕只是 ?v=1 和 ?v=2,就被视为两个模块,各自下载、解析、执行。
常见错误做法:
- 开发时用
import('./utils.js?t=' + Date.now())——每次生成新 URL,彻底绕过模块缓存,导致重复执行 - 构建后未做内容哈希,上线新版本但 URL 不变,用户永远跑旧逻辑
- CDN 或代理层重写 URL(如去掉查询参数),导致模块 URL 实际被标准化,意外共享缓存项
如何让模块缓存既高效又可控
关键不是“避免缓存”,而是让缓存行为可预测、可更新。推荐组合策略:
- 静态资源(如
utils.js)用构建时内容哈希命名:utils.a8f3b2d.js,URL 变则模块缓存自然失效 - 动态模块(如按需加载的路由组件)若需强制更新,加稳定哈希查询参数:
./route.js?hash=9e8c2a,而非时间戳 - 避免在模块顶层写副作用逻辑;把初始化包装成函数(如
init()),由入口模块统一调用,方便控制执行时机 - 调试时想清空模块缓存?唯一可靠方式是改 URL —— 刷新页面+硬重载(
Ctrl+Shift+R)无效,location.reload()也无效
file:// 协议下模块缓存根本不会启动
本地双击打开 HTML 文件(地址栏显示 file:///)时,所有 type="module" 脚本会被浏览器直接拒绝加载,控制台可能报 CORS 错误或静默失败。这不是缓存问题,是浏览器安全限制——模块机制依赖同源网络上下文,file:// 不被视为合法来源。
所以:
- 开发阶段务必用本地服务器(如
python -m http.server或 Vite dev server) - 不要在
file://下测试模块加载、缓存或import()行为,结果不可信 - 即便你看到脚本没报错,也可能根本没执行,
console.log不会输出
模块缓存最易被忽略的点在于:它不可清除、不可覆盖、不响应任何 HTTP 头,只认 URL。一旦部署上线,URL 就是缓存的唯一身份证——改错一个字符,就等于多开一个模块实例;漏掉一个哈希,就等于把旧逻辑钉死在用户内存里。
前端入门到VUE实战笔记:立即使用
在学习笔记中,你将探索 前端 的入门与实战技巧!











