本文针对HTML文件中全局工具库脚本被浏览器缓存导致版本更新失效的问题,详细解析了三种解决方案。重点介绍了通过JS动态创建script节点并配合轮询机制实现同步加载的方法,分析了在URL后添加随机参数的局限性,并指出Chrome 55起document.write注入script已被限制。旨在帮助开发者在无需频繁发版审核的情况下,有效管理前端脚本缓存。
场景
假设我们有一个工具库,通过 script 标签引入并挂载到全局环境中,该库对外暴露一个包含多个方法的全局对象 gTool。其典型引入方式如下所示:
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<script type="text/javascript" src="https://xxxx/common/tool.min.js"></script> // [!code hl]
</head>
<body>
<div id="app"><!--app-html--></div>
<script type="module" src="/src/main.ts"></script>
</body>
</html>
随后,在 main.ts 主逻辑文件中,即可直接调用 gTool.jumpLink 等方法。
gTool.jumpLink({})
问题
然而,在实际开发中常遇到如下困境:由于业务迭代频繁,我们需要不断修改 gTool 的代码并重新上传至 CDN。但用户通过 HTML 文件访问时,浏览器往往直接读取本地缓存的 gTool 链接,而不会向服务器发起新请求。这导致用户手中的 gTool 版本可能滞后,包含的 bug 无法得到及时修复。
因此,核心诉求变为:如何有效禁止浏览器对脚本进行缓存。
方法
本文尝试并对比了三种解决思路。
1. 动态创建 script 标签并插入 body
最终推荐采用此方案,既满足需求,线上运行也稳定无误。
可以通过 JavaScript 动态创建 script 节点,并将其插入 DOM 中。但需注意,动态创建的 script 脚本默认是异步加载的,不会阻塞浏览器后续 JS 代码的执行。
如下代码示例中,控制台永远会先打印 'body 执行',后打印 'tool 脚本执行'。这种异步特性导致在脚本加载完成前获取全局 gTool 对象时会报错。
<!DOCTYPE html>
<html>
<head>
<script>
var htmlScriptScript = document.createElement('script')
htmlScriptScript.type = 'text/javascript'
htmlScriptScript.src = 'https://xxxx/common/tool.min.js?a=' + Math.random()
htmlScriptScript.onload = function () {
console.error('tool 脚本执行')
}
document.head.appendChild(htmlScriptScript)
</script>
</head>
<body>
<div id="app"><!--app-html--></div>
<script>
console.error('body 执行')
</script>
<script type="module" src="/src/main.ts"></script>
</body>
</html>
为确保在 gTool 加载完成后才执行后续逻辑,我们可以封装一个阻塞函数。实现如下:
<!DOCTYPE html>
<html>
<head>
<script>
var htmlScriptScript = document.createElement('script')
htmlScriptScript.type = 'text/javascript'
htmlScriptScript.src = 'https://xxxx/common/tool.min.js?a=' + Math.random()
htmlScriptScript.onload = function () {
window.gTool = gTool
}
document.head.appendChild(htmlScriptScript)
</script>
</head>
<body>
<div id="app"><!--app-html--></div>
<script>
+ async function main() {
+ function awaitTool() {
+ return new Promise(resolve => {
+ const interval = setInterval(() => {
+ if (window.gTool) {
+ clearInterval(interval)
+ resolve(null)
+ }
+ }, 50)
+ })
+ }
+ await awaitTool();
+ // other code
+ }
+ main()
</script>
<script type="module" src="/src/main.ts"></script>
</body>
</html>
main() 该函数会每隔 50ms 检查 window 对象上是否存在 gTool。若存在,说明脚本已加载完毕,可继续执行后续代码;否则,递归调用 main(),直到 gTool 加载完成。
2. 在 URL 地址后添加随机参数
这并非理想的解决方案。
尽管该方法可行,但每次更新都需要触发发版流程。由于发版涉及审核流程且存在次数限制,这种方式并不灵活。
以下两种引入方式效果一致,区别在于第二种在每次重新打包时会自动刷新时间戳参数。
<script type="text/javascript" src="https://xxxx/common/tool.min.js?time=1124112341"></script> // [!code hl]
<!-- 或者 -->
<script type="text/javascript" src="https://xxxx/common/tool.min.js?time=<%=Math.random()%>"></script>
3. 使用 document.write()
此方法不可行。
试图利用 document.write API 来防止 .html 文件缓存 script 脚本。
根据 MDN 文档说明,自 Chrome 55 版本起,Chrome 浏览器(可能包括其他现代浏览器)不再执行通过 document.write() 注入的 <script>,此举旨在防止使用 2G 网络连接的用户无法获取 HTTP 缓存。因此,利用此方法动态注入 <script> 脚本是行不通的。

<script>
document.write("<script src='https://xxxx/common/tool.min.js?rnd=" + Math.random() + "'></s " + " cript> ")
</script>
以上即为 .html 文件中防止 script 脚本缓存的三种方法详解,更多关于 HTML 防止脚本缓存的资料,请持续关注本站其他相关文章!